🎉 init(init):初始化仓库
This commit is contained in:
90
Jenkinsfile
vendored
Normal file
90
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
stages {
|
||||||
|
stage('develop build and upload') {
|
||||||
|
when {
|
||||||
|
branch "develop"
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
sh '''
|
||||||
|
docker login http://172.16.102.3:30648 -u ${reg_username} -p ${reg_passwd}
|
||||||
|
docker build -t 172.16.102.3:30648/maimai/${service_name}:latest .
|
||||||
|
docker push 172.16.102.3:30648/maimai/${service_name}:latest
|
||||||
|
kubectl rollout restart deployment ${service_name} -n dev
|
||||||
|
'''
|
||||||
|
} catch(err) {
|
||||||
|
sh 'exit 1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('test build only') {
|
||||||
|
when {
|
||||||
|
expression {ref ==~ 'refs/tags/test-.*' }
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
sh '''
|
||||||
|
docker login http://172.16.102.3:30648 -u ${reg_username} -p ${reg_passwd}
|
||||||
|
docker build -t 172.16.102.3:30648/maimai/${service_name}:${result} .
|
||||||
|
docker push 172.16.102.3:30648/maimai/${service_name}:${result}
|
||||||
|
'''
|
||||||
|
} catch(err) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('master build only') {
|
||||||
|
when {
|
||||||
|
expression {ref ==~ 'refs/tags/prod-.*' }
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
sh '''
|
||||||
|
docker login http://172.16.102.3:30648 -u ${reg_username} -p ${reg_passwd}
|
||||||
|
docker build -t 172.16.102.3:30648/maimai/${service_name}:${result} .
|
||||||
|
docker push 172.16.102.3:30648/maimai/${service_name}:${result}
|
||||||
|
'''
|
||||||
|
} catch(err) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
reg_username = 'maimai'
|
||||||
|
reg_passwd = 'M9hUQk4Ti0l0lHZi'
|
||||||
|
service_name = 'xj-irrigation-model'
|
||||||
|
result = sh(script: """echo $ref | awk -F"/" '{print \$NF}'""", returnStdout: true).trim()
|
||||||
|
git_commit_msg = sh (script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
|
||||||
|
git_commit_user = sh (script: 'git show -s --pretty=%an', returnStdout: true).trim()
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
success {
|
||||||
|
sh """curl --location --request POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=d31913db-bb53-4081-9e2d-f707111dbee1' \
|
||||||
|
--data-raw '{\"msgtype\":\"text\",\"text\":{\"content\":\"成功: [${env.JOB_NAME} [${env.BUILD_NUMBER}]](${git_commit_msg}) @${git_commit_user}\"}}'"""
|
||||||
|
}
|
||||||
|
failure {
|
||||||
|
sh """curl --location --request POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=d31913db-bb53-4081-9e2d-f707111dbee1' \
|
||||||
|
--data-raw '{\"msgtype\":\"text\",\"text\":{\"content\":\"失败: [${env.JOB_NAME} [${env.BUILD_NUMBER}]](${git_commit_msg}) @${git_commit_user}\"}}'"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
triggers {
|
||||||
|
GenericTrigger(
|
||||||
|
genericVariables: [[key: 'ref', value: '$.ref']],
|
||||||
|
causeString: 'Triggered on $ref',
|
||||||
|
token: 'xj-irrigation-model',
|
||||||
|
printContributedVariables: true,
|
||||||
|
printPostContent: true,
|
||||||
|
silentResponse: false,
|
||||||
|
regexpFilterText: '$ref/'+BRANCH_NAME,
|
||||||
|
regexpFilterExpression: '(^refs/heads/develop/develop$)|(^refs/tags/test-.*/test$)|(^refs/tags/prod-.*/master$)'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
36
README.en.md
Normal file
36
README.en.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# xj-irrigation-model
|
||||||
|
|
||||||
|
#### Description
|
||||||
|
智能水肥模型
|
||||||
|
|
||||||
|
#### Software Architecture
|
||||||
|
Software architecture description
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### Instructions
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### Contribution
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create Feat_xxx branch
|
||||||
|
3. Commit your code
|
||||||
|
4. Create Pull Request
|
||||||
|
|
||||||
|
|
||||||
|
#### Gitee Feature
|
||||||
|
|
||||||
|
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||||
|
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||||
|
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||||
|
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||||
|
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||||
|
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||||
38
README.md
38
README.md
@@ -1,3 +1,37 @@
|
|||||||
# irrigation-model
|
# xj-irrigation-model
|
||||||
|
|
||||||
判断是否需要灌溉
|
#### 介绍
|
||||||
|
智能水肥模型
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
软件架构说明
|
||||||
|
|
||||||
|
|
||||||
|
#### 安装教程
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
1. Fork 本仓库
|
||||||
|
2. 新建 Feat_xxx 分支
|
||||||
|
3. 提交代码
|
||||||
|
4. 新建 Pull Request
|
||||||
|
|
||||||
|
|
||||||
|
#### 特技
|
||||||
|
|
||||||
|
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||||
|
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||||
|
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||||
|
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||||
|
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||||
|
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||||
|
|||||||
11
config.nifi
Normal file
11
config.nifi
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[postgresql]
|
||||||
|
host = localhost
|
||||||
|
port = 5432
|
||||||
|
database = datastore
|
||||||
|
user = postgres
|
||||||
|
password = postgres
|
||||||
|
schema = public
|
||||||
|
tablename=irrigation_data
|
||||||
|
|
||||||
|
[logging]
|
||||||
|
level = INFO
|
||||||
9
dockerfile
Normal file
9
dockerfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM python:3.10.5
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
ENTRYPOINT [ "python", "main.py" ]
|
||||||
|
EXPOSE 8001
|
||||||
0
irrgiation/__init__.py
Normal file
0
irrgiation/__init__.py
Normal file
BIN
irrgiation/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
irrgiation/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
irrgiation/__pycache__/crop_growth_stages.cpython-310.pyc
Normal file
BIN
irrgiation/__pycache__/crop_growth_stages.cpython-310.pyc
Normal file
Binary file not shown.
BIN
irrgiation/__pycache__/dailyEvaporation.cpython-310.pyc
Normal file
BIN
irrgiation/__pycache__/dailyEvaporation.cpython-310.pyc
Normal file
Binary file not shown.
BIN
irrgiation/__pycache__/db_connect.cpython-310.pyc
Normal file
BIN
irrgiation/__pycache__/db_connect.cpython-310.pyc
Normal file
Binary file not shown.
BIN
irrgiation/__pycache__/irrigateDecisionDemo.cpython-310.pyc
Normal file
BIN
irrgiation/__pycache__/irrigateDecisionDemo.cpython-310.pyc
Normal file
Binary file not shown.
BIN
irrgiation/__pycache__/mathuntils.cpython-310.pyc
Normal file
BIN
irrgiation/__pycache__/mathuntils.cpython-310.pyc
Normal file
Binary file not shown.
BIN
irrgiation/__pycache__/soil_Ke.cpython-310.pyc
Normal file
BIN
irrgiation/__pycache__/soil_Ke.cpython-310.pyc
Normal file
Binary file not shown.
BIN
irrgiation/__pycache__/weatherAndSoilDataRequest.cpython-310.pyc
Normal file
BIN
irrgiation/__pycache__/weatherAndSoilDataRequest.cpython-310.pyc
Normal file
Binary file not shown.
68
irrgiation/crop_growth_stages.py
Normal file
68
irrgiation/crop_growth_stages.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
crop_growth_stages={
|
||||||
|
'甘草': {
|
||||||
|
'initial': {
|
||||||
|
'root_length': {5: 0.8, 10: 0.2}, # 主根长:80%概率在0-15cm,平均约8cm
|
||||||
|
'duration_days': 20, # 6月播种后1-2周
|
||||||
|
'kc': 0.40, # 作物系数
|
||||||
|
'time_range': '2025-07-08至2025-07-28',
|
||||||
|
'hight':0.03,#m
|
||||||
|
'k_rain':0.25
|
||||||
|
},
|
||||||
|
'development': {
|
||||||
|
'root_length': {20: 0.6, 45: 0.4}, # 主根长:60%概率在20cm,40%概率在45cm,平均约30cm
|
||||||
|
'duration_days': 42, # 发芽后-8月20日左右(约2个月)
|
||||||
|
'kc': 0.9, # 作物系数
|
||||||
|
'time_range': '2025-07-29至2025-09-10',
|
||||||
|
'hight':0.15,
|
||||||
|
'k_rain':0.25
|
||||||
|
},
|
||||||
|
'mid': {
|
||||||
|
'root_length': {30: 0.5, 80: 0.5}, # 主根长:50%概率在30cm,50%概率在80cm,平均约55cm
|
||||||
|
'duration_days': 30, # 8月20日-9月下旬
|
||||||
|
'kc': 1.1, # 作物系数
|
||||||
|
'time_range': '2025-09-11至2025-10-10',
|
||||||
|
'hight':0.45,
|
||||||
|
'soil_evaporation_depth': 0.65,
|
||||||
|
'k_rain':0.4
|
||||||
|
},
|
||||||
|
'growth_slowly': {
|
||||||
|
'root_length':{40: 0.4,60:0.4, 80: 0.2}, # 根据数据估算平均主根长
|
||||||
|
'duration_days': 31, # 9月下旬-10月下旬
|
||||||
|
'kc': 0.6,
|
||||||
|
'time_range': '2025-10-11至2025-11-10',
|
||||||
|
'hight':0.6
|
||||||
|
},
|
||||||
|
'dormant_period': {
|
||||||
|
'root_length': {40: 0.3,60:0.4, 80: 0.2}, # 冬季休眠,主根基本停止生长
|
||||||
|
'duration_days': 140, # 11月-次年3月
|
||||||
|
'kc': 0.4,
|
||||||
|
'time_range': '2025-11-11至2026-03-31',
|
||||||
|
'hight':0.8,
|
||||||
|
'k_rain':0.5
|
||||||
|
},
|
||||||
|
'fanqing_year2': {
|
||||||
|
'root_length': {40: 0.2,60:0.6, 80: 0.2}, # 返青期主根长变化不大
|
||||||
|
'duration_days': 30, # 3月下旬-4月下旬
|
||||||
|
'kc': 0.35,
|
||||||
|
'time_range': '2026-04-01至2026-04-30',
|
||||||
|
'hight':0.5,
|
||||||
|
'k_rain':0.5
|
||||||
|
},
|
||||||
|
'development_year2': {
|
||||||
|
'root_length': {40: 0.1,60:0.7, 80: 0.2}, # 根据数据估算平均主根长
|
||||||
|
'duration_days': 90, # 4月下旬-7月下旬
|
||||||
|
'kc': 0.9,
|
||||||
|
'time_range': '2026-05-01至2026-07-30',
|
||||||
|
'hight':0.45,
|
||||||
|
'k_rain':0.65
|
||||||
|
},
|
||||||
|
'maturity_year2': {
|
||||||
|
'root_length':{40: 0.1,60:0.6, 80: 0.3}, # 根据数据估算平均主根长
|
||||||
|
'duration_days': 40, # 7月下旬-8月下旬
|
||||||
|
'kc': 0.6,
|
||||||
|
'time_range': '2026-08-01至2026-09-10',
|
||||||
|
'hight':0.65,
|
||||||
|
'k_rain':0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
365
irrgiation/dailyEvaporation.py
Normal file
365
irrgiation/dailyEvaporation.py
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pyeto import fao,convert
|
||||||
|
from pyeto.fao import svp_from_t, avp_from_rhmean, delta_svp, psy_const,atm_pressure
|
||||||
|
import pandas as pd
|
||||||
|
#1、计算日参考蒸散量
|
||||||
|
def calculateET0(tmean, tmax, tmin, rh_mean, wind_speed, solar_rad, elevation, lat, doy):
|
||||||
|
# 计算饱和水汽压和实际水汽压
|
||||||
|
svp_max = svp_from_t(tmax)
|
||||||
|
svp_min = svp_from_t(tmin)
|
||||||
|
svp_mean =(svp_max+svp_min)/2
|
||||||
|
avp = avp_from_rhmean(svp_max, svp_min, rh_mean)
|
||||||
|
|
||||||
|
# 计算净辐射(简化版)
|
||||||
|
# 将纬度从度转换为弧度
|
||||||
|
lat_rad = convert.deg2rad(lat)
|
||||||
|
sol_dec = fao.sol_dec(doy)
|
||||||
|
sha = fao.sunset_hour_angle(lat_rad, sol_dec)
|
||||||
|
ird = fao.inv_rel_dist_earth_sun(doy)
|
||||||
|
et_rad = fao.et_rad(lat_rad, sol_dec, sha, ird)
|
||||||
|
cs_rad = fao.cs_rad(elevation, et_rad)
|
||||||
|
nisr = fao.net_in_sol_rad(solar_rad)
|
||||||
|
nolr = fao.net_out_lw_rad(tmin, tmax, solar_rad, cs_rad, avp)
|
||||||
|
rn = fao.net_rad(nisr, nolr)
|
||||||
|
deltasvp= delta_svp(tmean)
|
||||||
|
psy = psy_const(atm_pressure(elevation))
|
||||||
|
# 计算ET₀
|
||||||
|
et0 = fao.fao56_penman_monteith(
|
||||||
|
net_rad=rn,
|
||||||
|
t=tmean,
|
||||||
|
ws=wind_speed,
|
||||||
|
svp=svp_mean,
|
||||||
|
avp=avp,
|
||||||
|
delta_svp=deltasvp,
|
||||||
|
psy=psy
|
||||||
|
)
|
||||||
|
return et0
|
||||||
|
def cal_et0_List(planting_date,monitor_date,date_params):
|
||||||
|
et0_list = []
|
||||||
|
current_date = planting_date
|
||||||
|
while current_date < monitor_date:
|
||||||
|
result_params=date_params[current_date.strftime("%Y-%m-%d")]
|
||||||
|
et0=calculateET0(result_params['tmean'], result_params['tmax'], result_params['tmin'], result_params['rh_mean'], result_params['wind_speed'],
|
||||||
|
result_params['solar_rad'], result_params['elevation'],result_params['lat'], result_params['doy'])
|
||||||
|
et0_list.append(et0)
|
||||||
|
current_date += timedelta(days=1) # 增加一天
|
||||||
|
return et0_list
|
||||||
|
|
||||||
|
|
||||||
|
#计算实际每天作物蒸散量
|
||||||
|
# 1. 定义甘草生育阶段与作物系数(参考苜蓿)
|
||||||
|
# growth_stages = {
|
||||||
|
# "initial": {"duration_days": 20, "kc": 0.40}, # 生长初期
|
||||||
|
# "development": {"duration_days": 40, "kc": 0.90}, #生长快速期
|
||||||
|
# "mid": {"duration_days": 20, "kc": 1.1}, # 生长缓慢期
|
||||||
|
# "growth_slowly": {"duration_days": 30, "kc": 0.6}, # 生长缓慢期
|
||||||
|
# "dormant_period": {"duration_days": 150, "kc": 0.40}, # 营养储备期
|
||||||
|
# "fanqing_year2":{"duration_days": 30, "kc": 0.35},#返青期
|
||||||
|
# "development_year2":{"duration_days": 90, "kc": 0.9}, #第二年快速生长期
|
||||||
|
# "maturity _year2": {"duration_days": 40, "kc": 0.6}, # 第二年成熟期
|
||||||
|
# }
|
||||||
|
growth_stages_roots1 = {
|
||||||
|
'initial': {
|
||||||
|
'root_length': {5: 0.8, 10: 0.2}, # 主根长:80%概率在0-15cm,平均约8cm
|
||||||
|
'duration_days': 23, # 6月播种后1-2周
|
||||||
|
'kc': 0.40, # 作物系数
|
||||||
|
'time_range': '2025-06-17至2025-07-10',
|
||||||
|
'hight':0.03,#m
|
||||||
|
'k_rain':0.25
|
||||||
|
},
|
||||||
|
'development': {
|
||||||
|
'root_length': {20: 0.6, 45: 0.4}, # 主根长:60%概率在20cm,40%概率在45cm,平均约30cm
|
||||||
|
'duration_days': 40, # 发芽后-8月20日左右(约2个月)
|
||||||
|
'kc': 0.9, # 作物系数
|
||||||
|
'time_range': '2025-07-10至2025-08-20',
|
||||||
|
'hight':0.15,
|
||||||
|
'k_rain':0.25
|
||||||
|
},
|
||||||
|
'mid': {
|
||||||
|
'root_length': {30: 0.5, 80: 0.5}, # 主根长:50%概率在30cm,50%概率在80cm,平均约55cm
|
||||||
|
'duration_days': 30, # 8月20日-9月下旬
|
||||||
|
'kc': 1.1, # 作物系数
|
||||||
|
'time_range': '2025-08-21至2025-09-20',
|
||||||
|
'hight':0.45,
|
||||||
|
'soil_evaporation_depth': 0.65,
|
||||||
|
'k_rain':0.4
|
||||||
|
},
|
||||||
|
'growth_slowly': {
|
||||||
|
'root_length':{40: 0.4,60:0.4, 80: 0.2}, # 根据数据估算平均主根长
|
||||||
|
'duration_days': 30, # 9月下旬-10月下旬
|
||||||
|
'kc': 0.6,
|
||||||
|
'time_range': '2025-09-21至2025-10-20',
|
||||||
|
'hight':0.6
|
||||||
|
},
|
||||||
|
'dormant_period': {
|
||||||
|
'root_length': {40: 0.3,60:0.4, 80: 0.2}, # 冬季休眠,主根基本停止生长
|
||||||
|
'duration_days': 160, # 11月-次年3月
|
||||||
|
'kc': 0.4,
|
||||||
|
'time_range': '2025-10-21至2026-03-30',
|
||||||
|
'hight':0.8,
|
||||||
|
'k_rain':0.5
|
||||||
|
},
|
||||||
|
'fanqing_year2': {
|
||||||
|
'root_length': {40: 0.2,60:0.6, 80: 0.2}, # 返青期主根长变化不大
|
||||||
|
'duration_days': 30, # 3月下旬-4月下旬
|
||||||
|
'kc': 0.35,
|
||||||
|
'time_range': '2026-04-01至2026-04-30',
|
||||||
|
'hight':0.5,
|
||||||
|
'k_rain':0.5
|
||||||
|
},
|
||||||
|
'development_year2': {
|
||||||
|
'root_length': {40: 0.1,60:0.7, 80: 0.2}, # 根据数据估算平均主根长
|
||||||
|
'duration_days': 90, # 4月下旬-7月下旬
|
||||||
|
'kc': 0.9,
|
||||||
|
'time_range': '2026-05-01至2026-07-30',
|
||||||
|
'hight':0.45,
|
||||||
|
'k_rain':0.65
|
||||||
|
},
|
||||||
|
'maturity_year2': {
|
||||||
|
'root_length':{40: 0.1,60:0.6, 80: 0.3}, # 根据数据估算平均主根长
|
||||||
|
'duration_days': 40, # 7月下旬-8月下旬
|
||||||
|
'kc': 0.6,
|
||||||
|
'time_range': '2026-08-01至2026-09-10',
|
||||||
|
'hight':0.65,
|
||||||
|
'k_rain':0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# growth_stages_roots = {
|
||||||
|
# 'initial': {
|
||||||
|
# 'root_length': {5: 0.8, 10: 0.2}, # 主根长:80%概率在0-15cm,平均约8cm
|
||||||
|
# 'duration_days': 20, # 6月播种后1-2周
|
||||||
|
# 'kc': 0.40, # 作物系数
|
||||||
|
# 'time_range': '2025-07-08至2025-07-28',
|
||||||
|
# 'hight':0.03,#m
|
||||||
|
# 'k_rain':0.25
|
||||||
|
# },
|
||||||
|
# 'development': {
|
||||||
|
# 'root_length': {20: 0.6, 45: 0.4}, # 主根长:60%概率在20cm,40%概率在45cm,平均约30cm
|
||||||
|
# 'duration_days': 42, # 发芽后-8月20日左右(约2个月)
|
||||||
|
# 'kc': 0.9, # 作物系数
|
||||||
|
# 'time_range': '2025-07-29至2025-09-10',
|
||||||
|
# 'hight':0.15,
|
||||||
|
# 'k_rain':0.25
|
||||||
|
# },
|
||||||
|
# 'mid': {
|
||||||
|
# 'root_length': {30: 0.5, 80: 0.5}, # 主根长:50%概率在30cm,50%概率在80cm,平均约55cm
|
||||||
|
# 'duration_days': 30, # 8月20日-9月下旬
|
||||||
|
# 'kc': 1.1, # 作物系数
|
||||||
|
# 'time_range': '2025-09-11至2025-10-10',
|
||||||
|
# 'hight':0.45,
|
||||||
|
# 'soil_evaporation_depth': 0.65,
|
||||||
|
# 'k_rain':0.4
|
||||||
|
# },
|
||||||
|
# 'growth_slowly': {
|
||||||
|
# 'root_length':{40: 0.4,60:0.4, 80: 0.2}, # 根据数据估算平均主根长
|
||||||
|
# 'duration_days': 31, # 9月下旬-10月下旬
|
||||||
|
# 'kc': 0.6,
|
||||||
|
# 'time_range': '2025-10-11至2025-11-10',
|
||||||
|
# 'hight':0.6
|
||||||
|
# },
|
||||||
|
# 'dormant_period': {
|
||||||
|
# 'root_length': {40: 0.3,60:0.4, 80: 0.2}, # 冬季休眠,主根基本停止生长
|
||||||
|
# 'duration_days': 140, # 11月-次年3月
|
||||||
|
# 'kc': 0.4,
|
||||||
|
# 'time_range': '2025-11-11至2026-03-31',
|
||||||
|
# 'hight':0.8,
|
||||||
|
# 'k_rain':0.5
|
||||||
|
# },
|
||||||
|
# 'fanqing_year2': {
|
||||||
|
# 'root_length': {40: 0.2,60:0.6, 80: 0.2}, # 返青期主根长变化不大
|
||||||
|
# 'duration_days': 30, # 3月下旬-4月下旬
|
||||||
|
# 'kc': 0.35,
|
||||||
|
# 'time_range': '2026-04-01至2026-04-30',
|
||||||
|
# 'hight':0.5,
|
||||||
|
# 'k_rain':0.5
|
||||||
|
# },
|
||||||
|
# 'development_year2': {
|
||||||
|
# 'root_length': {40: 0.1,60:0.7, 80: 0.2}, # 根据数据估算平均主根长
|
||||||
|
# 'duration_days': 90, # 4月下旬-7月下旬
|
||||||
|
# 'kc': 0.9,
|
||||||
|
# 'time_range': '2026-05-01至2026-07-30',
|
||||||
|
# 'hight':0.45,
|
||||||
|
# 'k_rain':0.65
|
||||||
|
# },
|
||||||
|
# 'maturity_year2': {
|
||||||
|
# 'root_length':{40: 0.1,60:0.6, 80: 0.3}, # 根据数据估算平均主根长
|
||||||
|
# 'duration_days': 40, # 7月下旬-8月下旬
|
||||||
|
# 'kc': 0.6,
|
||||||
|
# 'time_range': '2026-08-01至2026-09-10',
|
||||||
|
# 'hight':0.65,
|
||||||
|
# 'k_rain':0.5
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ---------------------------
|
||||||
|
# 2. 计算每日Kc值(根据生育阶段动态分配)
|
||||||
|
# ---------------------------
|
||||||
|
def get_daily_kc(growth_stages, total_days=423):
|
||||||
|
daily_kc = []
|
||||||
|
for stage in growth_stages.values():
|
||||||
|
daily_kc.extend([stage["kc"]] * stage["duration_days"])
|
||||||
|
# 如果总天数超出定义,用末期Kc填充剩余天数
|
||||||
|
if len(daily_kc) < total_days:
|
||||||
|
daily_kc.extend([growth_stages["maturity_year2"]["kc"]] * (total_days - len(daily_kc)))
|
||||||
|
return daily_kc[:total_days] # 截断至总天数
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# 3. 计算每日实际蒸散量ETc(得到历史列表)
|
||||||
|
# ---------------------------
|
||||||
|
def calculate_etc_List(et0_daily, planting_date,monitor_date, kc_daily):
|
||||||
|
"""
|
||||||
|
计算每日ETc(实际蒸散量)
|
||||||
|
:param eto_daily: 每日参考蒸散量列表(单位:cm/d)
|
||||||
|
:param planting_date: 播种日期,格式"YYYY-MM-DD"
|
||||||
|
:param kc_daily: 每日Kc值列表
|
||||||
|
:return: 日期列表、ETc列表
|
||||||
|
"""
|
||||||
|
start_date = planting_date
|
||||||
|
delta=monitor_date-start_date
|
||||||
|
days = delta.days
|
||||||
|
dates = [start_date + timedelta(days=i) for i in range(days)]
|
||||||
|
date_strings = [date.strftime("%Y-%m-%d") for date in dates]
|
||||||
|
kc_daily = kc_daily[:days]
|
||||||
|
etc = [eto * kc for eto, kc in zip(et0_daily, kc_daily)]
|
||||||
|
return etc,date_strings,kc_daily
|
||||||
|
#基于作物蒸散和土壤蒸发计算实际蒸散量
|
||||||
|
def calculate_etc_List_new(et0_daily, planting_date,monitor_date, kc_daily,ke_daily):
|
||||||
|
"""
|
||||||
|
计算每日ETc(实际蒸散量)
|
||||||
|
:param eto_daily: 每日参考蒸散量列表(单位:cm/d)
|
||||||
|
:param planting_date: 播种日期,格式"YYYY-MM-DD"
|
||||||
|
:param kc_daily: 每日Kc值列表
|
||||||
|
:return: 日期列表、ETc列表
|
||||||
|
"""
|
||||||
|
start_date = planting_date
|
||||||
|
delta=monitor_date-start_date
|
||||||
|
days = delta.days
|
||||||
|
dates = [start_date + timedelta(days=i) for i in range(days)]
|
||||||
|
date_strings = [date.strftime("%Y-%m-%d") for date in dates]
|
||||||
|
kc_daily = kc_daily[:days]
|
||||||
|
etc = [eto * (kc+ke) for eto, kc,ke in zip(et0_daily, kc_daily,ke_daily)]
|
||||||
|
return etc,date_strings,kc_daily
|
||||||
|
def calculate_etc_dayily(et0_daily, planting_date,monitor_date, kc_daily):
|
||||||
|
"""
|
||||||
|
计算每日ETc(实际蒸散量)
|
||||||
|
:param eto_daily: 每日参考蒸散量列表(单位:cm/d)
|
||||||
|
:param planting_date: 播种日期,格式"YYYY-MM-DD"
|
||||||
|
:param kc_daily: 每日Kc值列表
|
||||||
|
:return: 日期列表、ETc列表
|
||||||
|
"""
|
||||||
|
start_date = planting_date
|
||||||
|
delta=monitor_date-start_date
|
||||||
|
days = delta.days
|
||||||
|
# dates = [start_date + timedelta(days=i) for i in range(len(kc_daily))]
|
||||||
|
kc_daily = kc_daily[days]
|
||||||
|
etc = et0_daily*kc_daily
|
||||||
|
return etc
|
||||||
|
def export_etc_et0_date(et0_list,etc_list,dates,kc_list,ke_daily,soil_data,rain_daily,irrigate_daily,dk_name):
|
||||||
|
df = pd.DataFrame({
|
||||||
|
"Date": dates,
|
||||||
|
"ET0": [float(f"{x:.3f}") for x in et0_list],
|
||||||
|
"KC": kc_list,
|
||||||
|
"KE":ke_daily,
|
||||||
|
"ETc": [float(f"{x:.3f}") for x in etc_list],
|
||||||
|
"rain_effective":rain_daily,
|
||||||
|
"I_effective":irrigate_daily,
|
||||||
|
"VWC_fc":0.2884,
|
||||||
|
"VWC_wp":0.1086,
|
||||||
|
"dkbm":dk_name
|
||||||
|
})
|
||||||
|
df['VWC_SUR'] = 0.0
|
||||||
|
df['VWC_MID'] = 0.0
|
||||||
|
df['VWC_BOT'] = 0.0
|
||||||
|
for i in range(len(df)):
|
||||||
|
date=df.at[i, 'Date']
|
||||||
|
df.at[i, 'VWC_SUR']=round(float(soil_data["SOIL_MOISTURE_SURFACE"][date]),3)
|
||||||
|
df.at[i, 'VWC_MID'] = round(float(soil_data["SOIL_MOISTURE_MIDDLE"][date]),3)
|
||||||
|
df.at[i, 'VWC_BOT'] = round(float(soil_data["SOIL_MOISTURE_BOTTOM"][date]),3)
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def estimate_mad_from_et(max_et):
|
||||||
|
|
||||||
|
# 根据最大日蒸散量查表确定MAD值
|
||||||
|
if max_et < 2:
|
||||||
|
mad = 0.68
|
||||||
|
elif 2 <= max_et < 3:
|
||||||
|
mad = 0.58
|
||||||
|
elif 3 <= max_et < 4:
|
||||||
|
mad = 0.48
|
||||||
|
elif 4 <= max_et < 5:
|
||||||
|
mad = 0.4
|
||||||
|
elif 5 <= max_et < 6:
|
||||||
|
mad = 0.35
|
||||||
|
elif 6 <= max_et < 7:
|
||||||
|
mad = 0.33
|
||||||
|
elif 7 <= max_et < 8:
|
||||||
|
mad = 0.28
|
||||||
|
elif 8 <= max_et < 9:
|
||||||
|
mad = 0.25
|
||||||
|
else: # max_et >= 8
|
||||||
|
mad = 0.23
|
||||||
|
return mad
|
||||||
|
def calculate_weighted_soil_parameters(root_distribution, soil_layers):
|
||||||
|
# 初始化累计变量
|
||||||
|
sum_fc = 0.0
|
||||||
|
sum_wp = 0.0
|
||||||
|
# 处理每个根系分布层
|
||||||
|
for root_depth, root_ratio in root_distribution['root_length'].items():
|
||||||
|
# 找到包含该深度的土壤层
|
||||||
|
for layer in soil_layers:
|
||||||
|
min_depth, max_depth = layer['depth_range']
|
||||||
|
if min_depth <= root_depth < max_depth:
|
||||||
|
# 计算该层贡献 (简单比例分配)
|
||||||
|
layer_thickness = max_depth - min_depth
|
||||||
|
depth_in_layer = min(root_depth, max_depth) - min_depth
|
||||||
|
contribution_ratio = depth_in_layer / layer_thickness * root_ratio
|
||||||
|
sum_fc += layer['theta_33'] * contribution_ratio
|
||||||
|
sum_wp += layer['theta_1500'] * contribution_ratio
|
||||||
|
break
|
||||||
|
|
||||||
|
return sum_fc, sum_wp
|
||||||
|
def cal_stagebyDate(monitor_date,zwlx_growth_stages):
|
||||||
|
for stage, details in zwlx_growth_stages.items():
|
||||||
|
time_range_str = details['time_range']
|
||||||
|
if type(monitor_date) is str:
|
||||||
|
monitor_date= datetime.strptime(monitor_date, '%Y-%m-%d')
|
||||||
|
if '-' in time_range_str:
|
||||||
|
start_str, end_str = time_range_str.split('至')
|
||||||
|
start_date = datetime.strptime(start_str, '%Y-%m-%d')
|
||||||
|
end_date = datetime.strptime(end_str, '%Y-%m-%d')
|
||||||
|
if start_date <= monitor_date <= end_date:
|
||||||
|
return stage,details['root_length'],details['hight'],details['k_rain']
|
||||||
|
raise ValueError(f"监测日期 {monitor_date.strftime('%Y-%m-%d')} 不在任何生育阶段范围内")
|
||||||
|
#根据日期计算主根长度
|
||||||
|
def calculate_average_root_length(root_length_dist):
|
||||||
|
return sum(length * prob for length, prob in root_length_dist.items())
|
||||||
|
def cal_soil_fc_wp():
|
||||||
|
# 土壤分层数据 (33kPa和1500kPa对应的含水率)
|
||||||
|
soil_data = [
|
||||||
|
{'depth_range': (0, 5), 'theta_33': 0.296, 'theta_1500': 0.103},
|
||||||
|
{'depth_range': (5, 15), 'theta_33': 0.284, 'theta_1500': 0.104},
|
||||||
|
{'depth_range': (15, 30), 'theta_33': 0.286, 'theta_1500': 0.105},
|
||||||
|
{'depth_range': (30, 60), 'theta_33': 0.290, 'theta_1500': 0.114},
|
||||||
|
{'depth_range': (60, 100), 'theta_33': 0.286, 'theta_1500': 0.117},
|
||||||
|
{'depth_range': (100, 200), 'theta_33': 0.284, 'theta_1500': 0.114}
|
||||||
|
]
|
||||||
|
# theta_fc, theta_wp = calculate_weighted_soil_parameters(
|
||||||
|
# growth_stages_roots[stage],
|
||||||
|
# soil_data
|
||||||
|
# )
|
||||||
|
theta_fc=0.2884
|
||||||
|
theta_wp=0.1086
|
||||||
|
return theta_fc,theta_wp
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# #cal_et0_List(datetime(2026, 6, 18),datetime(2026, 6, 22))
|
||||||
|
et0_daily=[23,45,67,89]
|
||||||
|
# etc,dates,kc_daily=calculate_etc_List(et0_daily, datetime(2026, 6, 18),datetime(2026, 6, 22), get_daily_kc(growth_stages_roots))
|
||||||
|
# calculate_etc_dayily(45, datetime(2026, 6, 18), datetime(2026, 6, 22), get_daily_kc(growth_stages))
|
||||||
|
# df=export_etc_et0_date(et0_daily,etc,dates,kc_daily)
|
||||||
|
# print(df)
|
||||||
|
# cal_soil_fc_wp('initial')
|
||||||
|
# stage,details = cal_stagebyDate(datetime(2025, 7, 18))
|
||||||
|
# print(details)
|
||||||
|
# rd=calculate_average_root_length(details)
|
||||||
|
# print(rd)
|
||||||
199
irrgiation/db_connect.py
Normal file
199
irrgiation/db_connect.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Union, List
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import psycopg2
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from psycopg2 import sql
|
||||||
|
from psycopg2.extras import execute_batch
|
||||||
|
from sqlalchemy import create_engine, text
|
||||||
|
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
try:
|
||||||
|
project_nifi = Path(__file__).parent.parent / 'config.nifi'
|
||||||
|
config.read(project_nifi)
|
||||||
|
# 确保所有必要键都存在
|
||||||
|
required_keys = ['host', 'port', 'database', 'user', 'password','schema']
|
||||||
|
for key in required_keys:
|
||||||
|
if key not in config['postgresql']:
|
||||||
|
raise ValueError(f"Missing required config key: {key}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"host": config['postgresql']['host'],
|
||||||
|
"port": config['postgresql']['port'],
|
||||||
|
"database": config['postgresql']['database'],
|
||||||
|
"user": config['postgresql']['user'],
|
||||||
|
"password": config['postgresql']['password'],
|
||||||
|
"schema": config['postgresql']['schema'],
|
||||||
|
"tablename": config['postgresql']['tablename']
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"配置读取错误: {e}")
|
||||||
|
# 返回默认配置或退出
|
||||||
|
return None
|
||||||
|
def connect():
|
||||||
|
"""建立数据库连接"""
|
||||||
|
try:
|
||||||
|
config_params=get_config()
|
||||||
|
conn = psycopg2.connect(host=config_params['host'],port=config_params['port'],dbname=config_params['database'],
|
||||||
|
user=config_params['user'],password=config_params['password']
|
||||||
|
)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 连接失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# 根据条件查询
|
||||||
|
def query_postgresql_to_dataframe(
|
||||||
|
condition: Optional[dict] = None,
|
||||||
|
condition_operator: str = "AND",
|
||||||
|
columns: Union[str, List[str]] = "*",):
|
||||||
|
|
||||||
|
# 连接数据库
|
||||||
|
config_params = get_config()
|
||||||
|
conn = psycopg2.connect(host=config_params['host'], port=config_params['port'], dbname=config_params['database'],
|
||||||
|
user=config_params['user'], password=config_params['password']
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
# 构建列选择部分
|
||||||
|
if isinstance(columns, list):
|
||||||
|
columns_sql = sql.SQL(", ").join(sql.Identifier(col) for col in columns)
|
||||||
|
else:
|
||||||
|
columns_sql = sql.SQL(columns)
|
||||||
|
|
||||||
|
# 构建基础查询
|
||||||
|
query = sql.SQL("SELECT {} FROM {}").format(
|
||||||
|
columns_sql,
|
||||||
|
sql.Identifier(config_params['tablename'])
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构建条件部分(参数化)
|
||||||
|
params = {}
|
||||||
|
if condition:
|
||||||
|
conditions = []
|
||||||
|
for i, (key, value) in enumerate(condition.items()):
|
||||||
|
param_name = f"param_{i}"
|
||||||
|
conditions.append(sql.SQL("{} = %({})s").format(
|
||||||
|
sql.Identifier(key),
|
||||||
|
sql.SQL(param_name)
|
||||||
|
))
|
||||||
|
params[param_name] = value
|
||||||
|
|
||||||
|
where_clause = sql.SQL(" WHERE {}").format(
|
||||||
|
sql.SQL(f" {condition_operator} ").join(conditions)
|
||||||
|
)
|
||||||
|
query = query + where_clause
|
||||||
|
print(query)
|
||||||
|
# 执行查询
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(query, params)
|
||||||
|
if cursor.description:
|
||||||
|
columns = [desc[0] for desc in cursor.description]
|
||||||
|
data = cursor.fetchall()
|
||||||
|
return pd.DataFrame(data, columns=columns)
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# 插入库表
|
||||||
|
def dataframe_to_postgresql_batch(df,batch_size=1000):
|
||||||
|
"""
|
||||||
|
使用execute_batch批量插入DataFrame数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
df: 要插入的DataFrame
|
||||||
|
table_name: 目标表名
|
||||||
|
config_params: 数据库连接配置
|
||||||
|
batch_size: 每批插入的行数
|
||||||
|
"""
|
||||||
|
if df.empty:
|
||||||
|
print("DataFrame为空,无需插入")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取列名
|
||||||
|
columns = df.columns.tolist()
|
||||||
|
data = [tuple(x) for x in df.to_numpy()]
|
||||||
|
|
||||||
|
try:
|
||||||
|
config_params = get_config()
|
||||||
|
conn = psycopg2.connect(host=config_params['host'], port=config_params['port'],
|
||||||
|
dbname=config_params['database'],
|
||||||
|
user=config_params['user'], password=config_params['password']
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 构建INSERT语句
|
||||||
|
insert_query = sql.SQL("INSERT INTO {} ({}) VALUES ({})").format(
|
||||||
|
sql.Identifier(config_params['tablename']),
|
||||||
|
sql.SQL(', ').join(map(sql.Identifier, columns)),
|
||||||
|
sql.SQL(', ').join([sql.Placeholder()] * len(columns))
|
||||||
|
)
|
||||||
|
|
||||||
|
# 批量执行
|
||||||
|
execute_batch(cursor, insert_query, data, batch_size)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
print(f"批量插入时出错: {e}")
|
||||||
|
finally:
|
||||||
|
if conn is not None:
|
||||||
|
conn.close()
|
||||||
|
# 根据条件更新某个字段
|
||||||
|
def update_irrigation_data(date_value, id_value, field_to_update, new_value):
|
||||||
|
"""
|
||||||
|
更新表中单条记录的单个字段
|
||||||
|
|
||||||
|
参数:
|
||||||
|
db_url: 数据库连接字符串
|
||||||
|
table_name: 表名
|
||||||
|
date_field: 日期字段名
|
||||||
|
id_field: ID/地块字段名
|
||||||
|
date_value: 日期值
|
||||||
|
id_value: ID/地块值
|
||||||
|
field_to_update: 要更新的字段名
|
||||||
|
new_value: 新值
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 数据库配置
|
||||||
|
db_config = {
|
||||||
|
"db_url": "postgresql://postgres:postgres@localhost:5432/datastore",
|
||||||
|
"table_name": "irrigation_data",
|
||||||
|
"date_field": "Date",
|
||||||
|
"id_field": "dkbm"
|
||||||
|
}
|
||||||
|
engine = create_engine(db_config["db_url"])
|
||||||
|
table_name=db_config["table_name"]
|
||||||
|
date_field=db_config["date_field"]
|
||||||
|
id_field=db_config["id_field"]
|
||||||
|
with engine.begin() as conn: # 自动提交事务
|
||||||
|
# 使用参数化查询防止SQL注入
|
||||||
|
update_sql = text(f"""
|
||||||
|
UPDATE {table_name}
|
||||||
|
SET "{field_to_update}" = :new_value
|
||||||
|
WHERE "{date_field}" = :date_value AND "{id_field}" = :id_value""")
|
||||||
|
conn.execute(update_sql, {
|
||||||
|
'new_value': new_value,
|
||||||
|
'date_value': date_value,
|
||||||
|
'id_value': id_value
|
||||||
|
})
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"更新失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
engine.dispose() # 确保连接关闭
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
project_nifi = Path(__file__).parent.parent / 'config.nifi'
|
||||||
|
config.read(project_nifi)
|
||||||
620
irrgiation/irrigateDecisionDemo.py
Normal file
620
irrgiation/irrigateDecisionDemo.py
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
import statistics
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from irrgiation.db_connect import query_postgresql_to_dataframe, dataframe_to_postgresql_batch,update_irrigation_data
|
||||||
|
|
||||||
|
from irrgiation.weatherAndSoilDataRequest import getWeatherAndSoilData, loginAuth, dateToTimestamp, get_soil_data_dk1, \
|
||||||
|
getFutureWeather, weather_params, get_soil_data_dk2
|
||||||
|
from irrgiation.dailyEvaporation import get_daily_kc, cal_et0_List, calculate_etc_List, export_etc_et0_date, \
|
||||||
|
cal_soil_fc_wp, estimate_mad_from_et, cal_stagebyDate, calculate_average_root_length,calculate_etc_List_new
|
||||||
|
from irrgiation.mathuntils import day_of_year
|
||||||
|
from irrgiation.soil_Ke import calculate_ke,calculate_D_ei
|
||||||
|
from irrgiation.crop_growth_stages import crop_growth_stages
|
||||||
|
|
||||||
|
#解析数据接口,计算均值、最大、最小值
|
||||||
|
def analy_soil_data(data):
|
||||||
|
soil_result = {}
|
||||||
|
for key in data:
|
||||||
|
daily_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
for metric, entries in data[key].items():
|
||||||
|
for entry in entries:
|
||||||
|
dt = datetime.strptime(entry["datetime"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
date_key = dt.date() # 按日期分组
|
||||||
|
value = float(entry["value"])
|
||||||
|
daily_data[date_key][metric].append(value)
|
||||||
|
result = {}
|
||||||
|
for date, metrics in daily_data.items():
|
||||||
|
date_stats = {}
|
||||||
|
for metric, values in metrics.items():
|
||||||
|
date_stats[metric] = {
|
||||||
|
"max": max(values),
|
||||||
|
"min": min(values),
|
||||||
|
"mean": statistics.mean(values),
|
||||||
|
'count': len(values),
|
||||||
|
'sum': sum(values)
|
||||||
|
}
|
||||||
|
result[str(date)] = date_stats # 将日期转为字符串作为键
|
||||||
|
soil_result[key] = result
|
||||||
|
return soil_result
|
||||||
|
|
||||||
|
|
||||||
|
#获取地块6个传感器的土壤湿度均值
|
||||||
|
def analy_soil_data_mean(soil_data):
|
||||||
|
result = defaultdict(lambda: {
|
||||||
|
"SOIL_MOISTURE_SURFACE": [],
|
||||||
|
"SOIL_MOISTURE_MIDDLE": [],
|
||||||
|
"SOIL_MOISTURE_BOTTOM": []
|
||||||
|
|
||||||
|
})
|
||||||
|
# 填充数据
|
||||||
|
for sensor, dates in soil_data.items():
|
||||||
|
for date, layers in dates.items():
|
||||||
|
result[date]["SOIL_MOISTURE_SURFACE"].append(layers["SOIL_MOISTURE_SURFACE"]["mean"])
|
||||||
|
result[date]["SOIL_MOISTURE_MIDDLE"].append(layers["SOIL_MOISTURE_MIDDLE"]["mean"])
|
||||||
|
result[date]["SOIL_MOISTURE_BOTTOM"].append(layers["SOIL_MOISTURE_BOTTOM"]["mean"])
|
||||||
|
# 转换为普通字典
|
||||||
|
final_result = dict(result)
|
||||||
|
result_soil = {}
|
||||||
|
for date, layers in final_result.items():
|
||||||
|
result_soil[date] = {
|
||||||
|
"SOIL_MOISTURE_SURFACE_MEAN": sum(layers["SOIL_MOISTURE_SURFACE"]) / len(layers["SOIL_MOISTURE_SURFACE"]),
|
||||||
|
"SOIL_MOISTURE_MIDDLE_MEAN": sum(layers["SOIL_MOISTURE_MIDDLE"]) / len(layers["SOIL_MOISTURE_MIDDLE"]),
|
||||||
|
"SOIL_MOISTURE_BOTTOM_MEAN": sum(layers["SOIL_MOISTURE_BOTTOM"]) / len(layers["SOIL_MOISTURE_BOTTOM"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return final_result, result_soil
|
||||||
|
|
||||||
|
|
||||||
|
def analy_soil_dataNight_mean(soil_data):
|
||||||
|
daily_means = defaultdict(lambda: defaultdict(list))
|
||||||
|
# 计算平均值
|
||||||
|
for sensor, measurements in soil_data.items():
|
||||||
|
for measure_type, dates in measurements.items():
|
||||||
|
for date, value in dates.items():
|
||||||
|
daily_means[date][measure_type].append(float(value))
|
||||||
|
|
||||||
|
# 计算并格式化结果
|
||||||
|
result = {}
|
||||||
|
for date, measures in daily_means.items():
|
||||||
|
result[date] = {
|
||||||
|
f"{measure_type}_MEAN": round(sum(values) / len(values), 2)
|
||||||
|
for measure_type, values in measures.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
#获取地块6个传感器中土壤湿度最大值
|
||||||
|
def analy_soil_data_max(soil_data):
|
||||||
|
result = defaultdict(lambda: {
|
||||||
|
"SOIL_MOISTURE_SURFACE": [],
|
||||||
|
"SOIL_MOISTURE_MIDDLE": [],
|
||||||
|
"SOIL_MOISTURE_BOTTOM": []
|
||||||
|
|
||||||
|
})
|
||||||
|
# 填充数据
|
||||||
|
for sensor, dates in soil_data.items():
|
||||||
|
for date, layers in dates.items():
|
||||||
|
result[date]["SOIL_MOISTURE_SURFACE"].append(layers["SOIL_MOISTURE_SURFACE"]["mean"])
|
||||||
|
result[date]["SOIL_MOISTURE_MIDDLE"].append(layers["SOIL_MOISTURE_MIDDLE"]["mean"])
|
||||||
|
result[date]["SOIL_MOISTURE_BOTTOM"].append(layers["SOIL_MOISTURE_BOTTOM"]["mean"])
|
||||||
|
# 转换为普通字典
|
||||||
|
final_result = dict(result)
|
||||||
|
result_soil = {}
|
||||||
|
for date, layers in final_result.items():
|
||||||
|
result_soil[date] = {
|
||||||
|
"SOIL_MOISTURE_SURFACE_MEAN": max(layers["SOIL_MOISTURE_SURFACE"]),
|
||||||
|
"SOIL_MOISTURE_MIDDLE_MEAN": max(layers["SOIL_MOISTURE_MIDDLE"]),
|
||||||
|
"SOIL_MOISTURE_BOTTOM_MEAN": max(layers["SOIL_MOISTURE_BOTTOM"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return final_result, result_soil
|
||||||
|
|
||||||
|
|
||||||
|
def get_soil_data_night_eyveryDay(data):
|
||||||
|
daily_data = {}
|
||||||
|
for sersor in data:
|
||||||
|
data_depth = {}
|
||||||
|
for key in data[sersor]:
|
||||||
|
target_time = (21, 59, 59)
|
||||||
|
result = {} # 时、分、秒元组
|
||||||
|
daily_records = {} # 临时存储每天的所有记录,用于找不到22点数据时获取最新记录
|
||||||
|
for record in data[sersor][key]:
|
||||||
|
try:
|
||||||
|
dt = datetime.strptime(record["datetime"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
date_str = record["datetime"].split(" ")[0] # 提取日期部分
|
||||||
|
# 严格匹配 02:00:00
|
||||||
|
# 提取时间部分(时、分、秒)
|
||||||
|
current_time = (dt.hour, dt.minute, dt.second)
|
||||||
|
# 存储每天的所有记录(按时间排序)
|
||||||
|
if date_str not in daily_records:
|
||||||
|
daily_records[date_str] = []
|
||||||
|
daily_records[date_str].append((dt, record['value']))
|
||||||
|
# 比较时间部分
|
||||||
|
if current_time >= target_time:
|
||||||
|
if date_str not in result:
|
||||||
|
result[date_str] = record['value']
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
# 对于没有22点数据的日期,取当天最新的一条数据
|
||||||
|
for date_str in daily_records:
|
||||||
|
if date_str not in result and daily_records[date_str]:
|
||||||
|
daily_records[date_str].sort(key=lambda x: x[0]) # 按datetime排序
|
||||||
|
result[date_str] = daily_records[date_str][-1][1] # 取最后一个值
|
||||||
|
data_depth[key] = result
|
||||||
|
daily_data[sersor] = data_depth
|
||||||
|
return daily_data
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_weather_data1(data):
|
||||||
|
# 1. 按日期分组数据
|
||||||
|
daily_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
for metric, entries in data.items():
|
||||||
|
for entry in entries:
|
||||||
|
dt = datetime.strptime(entry["datetime"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
date_key = dt.date() # 按日期分组
|
||||||
|
if metric == "AIR_LUX":
|
||||||
|
value = float(entry["value_solar"])
|
||||||
|
else:
|
||||||
|
value = float(entry["value"])
|
||||||
|
daily_data[date_key][metric].append(value)
|
||||||
|
# 2. 计算每日统计值
|
||||||
|
result = {}
|
||||||
|
for date, metrics in daily_data.items():
|
||||||
|
date_stats = {}
|
||||||
|
for metric, values in metrics.items():
|
||||||
|
date_stats[metric] = {
|
||||||
|
"max": max(values),
|
||||||
|
"min": min(values),
|
||||||
|
"mean": statistics.mean(values),
|
||||||
|
'count': len(values),
|
||||||
|
'sum': sum(values)
|
||||||
|
}
|
||||||
|
result[str(date)] = date_stats # 将日期转为字符串作为键
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# 获取彭曼公式的入参
|
||||||
|
def get_params(start_date, monitor_date, data):
|
||||||
|
delta = monitor_date - start_date
|
||||||
|
days = delta.days
|
||||||
|
dates = [start_date + timedelta(days=i) for i in range(days)]
|
||||||
|
date_strings = [date.strftime("%Y-%m-%d") for date in dates]
|
||||||
|
date_params = {}
|
||||||
|
for date in date_strings:
|
||||||
|
result_params = get_weather_params(data, date)
|
||||||
|
date_params[date] = result_params
|
||||||
|
return date_params
|
||||||
|
|
||||||
|
|
||||||
|
def get_weather_params(data, date):
|
||||||
|
result = {}
|
||||||
|
elevation = 865 # 海拔(m)
|
||||||
|
lat = 41.34252110189814 # 纬度(度)
|
||||||
|
#计算当前日期位于当年的多少天
|
||||||
|
doy = day_of_year(date)
|
||||||
|
|
||||||
|
for item in data['AIR_LUX']:
|
||||||
|
item['value_solar'] = float(item['value']) * 0.0081 * 3600
|
||||||
|
data_tj = analyze_weather_data1(data)
|
||||||
|
data_tj_date = data_tj[date];
|
||||||
|
tmax = data_tj_date['AIR_TEMPERATURE']['max']
|
||||||
|
tmin = data_tj_date['AIR_TEMPERATURE']['min']
|
||||||
|
tmean = data_tj_date['AIR_TEMPERATURE']['mean']
|
||||||
|
rh_mean = data_tj_date['AIR_HUMIDITY']['mean']
|
||||||
|
wind_speed = data_tj_date['WIND_SPEED']['mean']
|
||||||
|
solar = data_tj_date['AIR_LUX']['sum'] / 1000000
|
||||||
|
result['tmax'] = round(tmax, 3)
|
||||||
|
result['tmin'] = round(tmin, 3)
|
||||||
|
result['tmean'] = round(tmean, 3)
|
||||||
|
result['rh_mean'] = round(rh_mean, 3)
|
||||||
|
result['wind_speed'] = round(wind_speed, 3)*0.94
|
||||||
|
result['solar_rad'] = round(solar, 3)
|
||||||
|
result['elevation'] = elevation
|
||||||
|
result['lat'] = round(lat, 3)
|
||||||
|
result['doy'] = doy
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# 判断是否需要灌溉
|
||||||
|
def isNeedirrigate(df, qx_coff, soil_coff,Di_1,zwlx_growth_stages):
|
||||||
|
df['RD'] = 0.0
|
||||||
|
df['Di'] = 0.0
|
||||||
|
df['Di-1'] = Di_1
|
||||||
|
df['mad'] = 0.0
|
||||||
|
df['irrigation_depth'] = 0.0
|
||||||
|
df['irrigation_acre'] = 0.0
|
||||||
|
df['isNeedirrigate'] = False
|
||||||
|
df['Reset_Flag'] = False
|
||||||
|
# df['irrigate_sd_predict'] = 0.0
|
||||||
|
df['f_t'] = 0.0
|
||||||
|
df['f_js'] = 0.0
|
||||||
|
df['f_soil'] = 0.0
|
||||||
|
for i in range(len(df)):
|
||||||
|
# 设置Di-1(前一天累计值)
|
||||||
|
if i > 0:
|
||||||
|
df.at[i, 'Di-1'] = df.at[i - 1, 'Di']
|
||||||
|
# 计算当前累计值(Di-1 + 当天ETc)
|
||||||
|
raw, rd, mad, k_rain = cal_raw(df.at[i, 'ETc'], df.at[i, 'Date'],zwlx_growth_stages)
|
||||||
|
if df.at[i, 'rain_effective']<5:
|
||||||
|
df.at[i, 'rain_effective'] = 0
|
||||||
|
else:
|
||||||
|
df.at[i, 'rain_effective'] = df.at[i, 'rain_effective'] * k_rain
|
||||||
|
current_cum = df.at[i, 'Di-1'] + df.at[i, 'ETc']- df.at[i, 'rain_effective']
|
||||||
|
df['RD'] = rd
|
||||||
|
df.at[i,'mad'] = mad
|
||||||
|
df.at[i, 'raw'] = round(raw, 3)
|
||||||
|
f_t = qx_coff[df.at[i, 'Date']]['qw']
|
||||||
|
f_js = qx_coff[df.at[i, 'Date']]['js']
|
||||||
|
f_soil = soil_coff[df.at[i, 'Date']]
|
||||||
|
raw_adj = cal_raw_adjust(raw, f_t, f_js, f_soil)
|
||||||
|
df.at[i, 'f_t'] = f_t
|
||||||
|
df.at[i, 'f_js'] = f_js
|
||||||
|
df.at[i, 'f_soil'] = f_soil
|
||||||
|
df.at[i, 'raw_adjust'] = round(raw_adj, 3)
|
||||||
|
# 判断是否需要重置
|
||||||
|
if current_cum > raw_adj:
|
||||||
|
df.at[i, 'Reset_Flag'] = True
|
||||||
|
df.at[i, 'irrigation_depth'] = round(current_cum, 3)
|
||||||
|
df.at[i, 'isNeedirrigate'] = True
|
||||||
|
df.at[i, 'Di'] = 0 # 保留当天ETc值
|
||||||
|
df.at[i, 'irrigation_acre'] = round((current_cum*667/1000),3)
|
||||||
|
else:
|
||||||
|
df.at[i, 'Di'] = round(current_cum, 3) # 正常累加
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def cal_raw(etc, monitor_date,zwlx_growth_stages):
|
||||||
|
theta_fc, theta_wp = cal_soil_fc_wp()
|
||||||
|
stage, details,hight,k_rain= cal_stagebyDate(monitor_date,zwlx_growth_stages)
|
||||||
|
rd = calculate_average_root_length(details)
|
||||||
|
mad = estimate_mad_from_et(etc)
|
||||||
|
raw = mad * (theta_fc - theta_wp) * rd * 10
|
||||||
|
return raw, rd,mad,k_rain
|
||||||
|
|
||||||
|
|
||||||
|
def cal_raw_adjust(raw, f_t, f_rain, f_soil):
|
||||||
|
raw_adjust = raw * f_soil * f_t * f_rain
|
||||||
|
return raw_adjust
|
||||||
|
|
||||||
|
|
||||||
|
def cal_qxcoefficient_adjust(start_date, monitor_date, qx_data):
|
||||||
|
|
||||||
|
qx_coefficient = {}
|
||||||
|
current_date = start_date
|
||||||
|
qxData_filter = {}
|
||||||
|
for key in qx_data:
|
||||||
|
if key == "AIR_TEMPERATURE" or key == "RAIN_FALL_REALTIME":
|
||||||
|
qxData_filter[key] = qx_data[key]
|
||||||
|
qxData_filter_tj = analyze_weather_data1(qxData_filter)
|
||||||
|
now_date = datetime.now().date()
|
||||||
|
while current_date < monitor_date:
|
||||||
|
qx_futureData = []
|
||||||
|
result = {}
|
||||||
|
if (current_date + timedelta(days=3)).date() < now_date:
|
||||||
|
start = datetime.strptime((current_date + timedelta(days=1)).strftime("%Y-%m-%d"), "%Y-%m-%d")
|
||||||
|
end = datetime.strptime((current_date + timedelta(days=3)).strftime("%Y-%m-%d"), "%Y-%m-%d")
|
||||||
|
# 筛选日期范围内的数据
|
||||||
|
filter = {
|
||||||
|
date: values
|
||||||
|
for date, values in qxData_filter_tj.items()
|
||||||
|
if start <= datetime.strptime(date, "%Y-%m-%d") <= end
|
||||||
|
}
|
||||||
|
qx_futureData = transform_data(filter)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# print(f"进入else分支: {current_date}")
|
||||||
|
qx_futureData = getFutureWeather(current_date)
|
||||||
|
except Exception as e:
|
||||||
|
# print(f"else分支异常: {e}")
|
||||||
|
raise # 保留异常以便调试
|
||||||
|
qw_mean = (qx_futureData['qw_day1']['mean'] + qx_futureData['qw_day2']['mean'] + qx_futureData['qw_day3']['mean']) / 3
|
||||||
|
js_sum = qx_futureData['js_day1']['max'] + qx_futureData['js_day2']['max'] + qx_futureData['js_day3']['max']
|
||||||
|
qw_coff = 1.0
|
||||||
|
js_coff = 1.0
|
||||||
|
if qw_mean < 10:
|
||||||
|
qw_coff = 1.1
|
||||||
|
elif 10 < qw_mean <= 25:
|
||||||
|
qw_coff = 1
|
||||||
|
elif 25 < qw_mean <= 30:
|
||||||
|
qw_coff = 0.85
|
||||||
|
else:
|
||||||
|
qw_coff = 0.75
|
||||||
|
if js_sum <= 5:
|
||||||
|
js_coff = 1.0 # 少雨
|
||||||
|
elif 5 < js_sum <= 10:
|
||||||
|
js_coff = 1.2 # 中雨
|
||||||
|
elif 10 < js_sum <= 20:
|
||||||
|
js_coff = 1.4 # 大雨
|
||||||
|
else:
|
||||||
|
js_coff = 1.6 # 暴雨
|
||||||
|
result['qw'] = qw_coff
|
||||||
|
result['js'] = js_coff
|
||||||
|
qx_coefficient[current_date.strftime("%Y-%m-%d")] = result
|
||||||
|
current_date = current_date + timedelta(days=1)
|
||||||
|
return qx_coefficient
|
||||||
|
|
||||||
|
|
||||||
|
def transform_data(original_data):
|
||||||
|
transformed = {}
|
||||||
|
# 处理温度数据 (qw_day)
|
||||||
|
for i, (date, metrics) in enumerate(original_data.items(), 1):
|
||||||
|
temp_data = metrics["AIR_TEMPERATURE"]
|
||||||
|
transformed[f"qw_day{i}"] = {
|
||||||
|
"max": round(temp_data["max"], 2),
|
||||||
|
"min": round(temp_data["min"], 2),
|
||||||
|
"mean": round(temp_data["mean"], 2),
|
||||||
|
"sum": round(temp_data["sum"], 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 处理降雨数据 (js_day)
|
||||||
|
for i, (date, metrics) in enumerate(original_data.items(), 1):
|
||||||
|
rain_data = metrics["RAIN_FALL_REALTIME"]
|
||||||
|
transformed[f"js_day{i}"] = {
|
||||||
|
"max": round(rain_data["max"], 2),
|
||||||
|
"min": round(rain_data["min"], 2),
|
||||||
|
"mean": round(rain_data["mean"], 2),
|
||||||
|
"sum": round(rain_data["sum"], 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformed
|
||||||
|
|
||||||
|
|
||||||
|
def cal_soil_coff(soil_data):
|
||||||
|
soil_data_coff = {}
|
||||||
|
for sensor in soil_data:
|
||||||
|
for date in soil_data[sensor]:
|
||||||
|
soil_cof = 1.0
|
||||||
|
sd_surf = float(soil_data['SOIL_MOISTURE_SURFACE'][date])
|
||||||
|
sd_mid = float(soil_data['SOIL_MOISTURE_MIDDLE'][date])
|
||||||
|
if sd_surf < 10:
|
||||||
|
soil_cof = 0.5
|
||||||
|
if 10 <sd_surf < 15:
|
||||||
|
soil_cof = 0.75
|
||||||
|
elif 15<sd_surf < 20:
|
||||||
|
soil_cof = 1.0
|
||||||
|
else:
|
||||||
|
soil_cof = 1.2
|
||||||
|
soil_data_coff[date] = soil_cof
|
||||||
|
return soil_data_coff
|
||||||
|
|
||||||
|
|
||||||
|
#将两块地的传感器数据分隔开
|
||||||
|
def get_soil_data_bydk(soil_data):
|
||||||
|
target_key = 7
|
||||||
|
dk1_soildata = {}
|
||||||
|
dk2_soildata = {}
|
||||||
|
|
||||||
|
for key, value in soil_data.items():
|
||||||
|
parts = key.split("号传感器")
|
||||||
|
if int(parts[0]) < target_key:
|
||||||
|
dk1_soildata[key] = value
|
||||||
|
else:
|
||||||
|
dk2_soildata[key] = value
|
||||||
|
return dk1_soildata, dk2_soildata
|
||||||
|
# 整生育期监测
|
||||||
|
def cal_dk_isNeegirrigate(zwlx_name,dk_name,start_date, monitor_date,Di_1):
|
||||||
|
auth_token = loginAuth()
|
||||||
|
date_start = start_date.strftime("%Y-%m-%d") + " 00:00:00.123"
|
||||||
|
date_end_qx=monitor_date
|
||||||
|
if (monitor_date + timedelta(days=3)).date()<= datetime.now().date():
|
||||||
|
date_end_qx=(monitor_date + timedelta(days=3)).strftime("%Y-%m-%d") + " 23:59:59.123"
|
||||||
|
else:
|
||||||
|
date_end_qx=datetime.now().date().strftime("%Y-%m-%d") + " 23:59:59.123"
|
||||||
|
date_end = monitor_date.strftime("%Y-%m-%d") + " 23:59:59.123"
|
||||||
|
startTs = dateToTimestamp(date_start)
|
||||||
|
endTs = dateToTimestamp(date_end)
|
||||||
|
zwlx_growth_stages = crop_growth_stages[zwlx_name]
|
||||||
|
kc_daily = get_daily_kc(zwlx_growth_stages)
|
||||||
|
# #获取气象数据
|
||||||
|
qxdata = getWeatherAndSoilData(auth_token, weather_params['device_id'], startTs, dateToTimestamp(date_end_qx), weather_params['agg'],
|
||||||
|
weather_params['interval'], weather_params['limit'], weather_params['orderBy'],
|
||||||
|
weather_params['keys'])
|
||||||
|
# # #彭曼公式气象入参
|
||||||
|
qx_params = get_params(start_date, monitor_date, qxdata)
|
||||||
|
# #获取土壤传感器数据
|
||||||
|
data = get_soil_data_dk1(auth_token, startTs, endTs)
|
||||||
|
soil_data = get_soil_data_night_eyveryDay(data)
|
||||||
|
dk1_soildata, dk2_soildata = get_soil_data_bydk(soil_data)
|
||||||
|
# 膜边探头优先原则,1号和7号传感器分别为1号地和5号地的膜边探头
|
||||||
|
dk1_soil_mean = dk1_soildata['1号传感器']
|
||||||
|
dk2_soil_mean = dk2_soildata['7号传感器']
|
||||||
|
# #计算参考蒸散量
|
||||||
|
et0_list = cal_et0_List(start_date, monitor_date, qx_params)
|
||||||
|
#计算土壤蒸发系数
|
||||||
|
ke_data = cal_ke_daily_list(start_date, monitor_date, et0_list, kc_daily, qxdata)
|
||||||
|
ke_daily = []
|
||||||
|
rain_daily=[]
|
||||||
|
irrigate_daily=[]
|
||||||
|
for date in sorted(ke_data.keys()):
|
||||||
|
ke_value = round(ke_data[date]['ke'],3)
|
||||||
|
rain_value= round(ke_data[date]['rain'],3)
|
||||||
|
irrigate_value = ke_data[date]['irrigation']
|
||||||
|
ke_daily.append(ke_value)
|
||||||
|
rain_daily.append(rain_value)
|
||||||
|
irrigate_daily.append(irrigate_value)
|
||||||
|
|
||||||
|
# #获取作物蒸散量
|
||||||
|
etc_list, date_strings, kc_list = calculate_etc_List_new(et0_list, start_date, monitor_date, kc_daily,ke_daily)
|
||||||
|
# # 获取未来3天气象数据
|
||||||
|
qx_coff = cal_qxcoefficient_adjust(start_date, monitor_date, qxdata)
|
||||||
|
if "1" in dk_name:
|
||||||
|
df = export_etc_et0_date(et0_list, etc_list, date_strings, kc_list,ke_daily, dk1_soil_mean,rain_daily,irrigate_daily,dk_name)
|
||||||
|
# 基于每天实测土壤相对湿度计算土壤系数
|
||||||
|
soil_coff = cal_soil_coff(dk1_soil_mean)
|
||||||
|
elif "5" in dk_name:
|
||||||
|
df = export_etc_et0_date(et0_list, etc_list, date_strings, kc_list,ke_daily, dk2_soil_mean,rain_daily,irrigate_daily,dk_name)
|
||||||
|
# 基于每天实测土壤相对湿度计算土壤系数
|
||||||
|
soil_coff = cal_soil_coff(dk2_soil_mean)
|
||||||
|
else:
|
||||||
|
return "该地块数据不存在"
|
||||||
|
# 判断是否需要灌溉
|
||||||
|
dataframe = isNeedirrigate(df, qx_coff, soil_coff,Di_1,zwlx_growth_stages)
|
||||||
|
random_uuid = uuid.uuid4()
|
||||||
|
uuid_str = str(random_uuid)
|
||||||
|
# df.to_csv(
|
||||||
|
# 'D:/悦柠/遥感/甘草/ceshi/' + dk_name + "_" + start_date.strftime("%Y-%m-%d") + "-" + monitor_date.strftime(
|
||||||
|
# "%Y-%m-%d")+"_"+uuid_str + ".csv", index=False) # CSV 文件
|
||||||
|
return dataframe
|
||||||
|
def cal_dk_isNeegirrigate_Day(zwlx_name,dk_name,soil_key,start_date, monitor_date,Di_1,weather_key):
|
||||||
|
auth_token = loginAuth()
|
||||||
|
date_start = start_date.strftime("%Y-%m-%d") + " 00:00:00.123"
|
||||||
|
date_end_qx=monitor_date
|
||||||
|
if (monitor_date + timedelta(days=3)).date()<= datetime.now().date():
|
||||||
|
date_end_qx=(monitor_date + timedelta(days=3)).strftime("%Y-%m-%d") + " 23:59:59.123"
|
||||||
|
else:
|
||||||
|
date_end_qx=datetime.now().date().strftime("%Y-%m-%d") + " 23:59:59.123"
|
||||||
|
date_end = monitor_date.strftime("%Y-%m-%d") + " 23:59:59.123"
|
||||||
|
startTs = dateToTimestamp(date_start)
|
||||||
|
endTs = dateToTimestamp(date_end)
|
||||||
|
zwlx_growth_stages=crop_growth_stages[zwlx_name]
|
||||||
|
kc_daily = get_daily_kc(zwlx_growth_stages)
|
||||||
|
# #获取气象数据
|
||||||
|
qxdata = getWeatherAndSoilData(auth_token, weather_key, startTs, dateToTimestamp(date_end_qx), weather_params['agg'],
|
||||||
|
weather_params['interval'], weather_params['limit'], weather_params['orderBy'],
|
||||||
|
weather_params['keys'])
|
||||||
|
# 获取土壤传感器数据
|
||||||
|
data = get_soil_data_dk2(auth_token, soil_key, startTs, endTs)
|
||||||
|
soil_data = get_soil_data_night_eyveryDay(data)
|
||||||
|
# dk1_soildata, dk2_soildata = get_soil_data_bydk(soil_data)
|
||||||
|
# 膜边探头优先原则,1号和7号传感器分别为1号地和5号地的膜边探头
|
||||||
|
dk_soil_mean = soil_data[soil_key]
|
||||||
|
if len(qxdata) !=0 and len(dk_soil_mean) !=0:
|
||||||
|
# # #彭曼公式气象入参
|
||||||
|
qx_params = get_params(start_date, monitor_date, qxdata)
|
||||||
|
# #计算参考蒸散量
|
||||||
|
et0_list = cal_et0_List(start_date, monitor_date, qx_params)
|
||||||
|
# 计算土壤蒸发系数
|
||||||
|
ke_data = cal_ke_daily_list(start_date, monitor_date, et0_list, kc_daily, qxdata, zwlx_growth_stages)
|
||||||
|
ke_daily = []
|
||||||
|
rain_daily = []
|
||||||
|
irrigate_daily = []
|
||||||
|
for date in sorted(ke_data.keys()):
|
||||||
|
ke_value = round(ke_data[date]['ke'], 3)
|
||||||
|
rain_value = round(ke_data[date]['rain'], 3)
|
||||||
|
irrigate_value = ke_data[date]['irrigation']
|
||||||
|
ke_daily.append(ke_value)
|
||||||
|
rain_daily.append(rain_value)
|
||||||
|
irrigate_daily.append(irrigate_value)
|
||||||
|
# #获取作物蒸散量
|
||||||
|
etc_list, date_strings, kc_list = calculate_etc_List_new(et0_list, start_date, monitor_date, kc_daily,
|
||||||
|
ke_daily)
|
||||||
|
# # 获取未来3天气象数据
|
||||||
|
qx_coff = cal_qxcoefficient_adjust(start_date, monitor_date, qxdata)
|
||||||
|
|
||||||
|
df = export_etc_et0_date(et0_list, etc_list, date_strings, kc_list, ke_daily, dk_soil_mean, rain_daily,
|
||||||
|
irrigate_daily, dk_name)
|
||||||
|
# 基于每天实测土壤相对湿度计算土壤系数
|
||||||
|
soil_coff = cal_soil_coff(dk_soil_mean)
|
||||||
|
# 判断是否需要灌溉
|
||||||
|
dataframe = isNeedirrigate(df, qx_coff, soil_coff, Di_1, zwlx_growth_stages)
|
||||||
|
return dataframe
|
||||||
|
elif len(qxdata) ==0 and len(dk_soil_mean) !=0:
|
||||||
|
raise ValueError("未查询到气象数据")
|
||||||
|
elif len(qxdata) !=0 and len(dk_soil_mean) ==0:
|
||||||
|
raise ValueError("未查询到土壤数据")
|
||||||
|
else:
|
||||||
|
raise ValueError("未查询到气象和土壤数据")
|
||||||
|
|
||||||
|
|
||||||
|
#计算土壤蒸发系数
|
||||||
|
def cal_ke_daily_list(start_date,monitor_date,et0_list,kc_list,qx_data,zwlx_growth_stages):
|
||||||
|
stage, details, hight ,k_rain= cal_stagebyDate(monitor_date,zwlx_growth_stages)
|
||||||
|
REW=9
|
||||||
|
I=0.0
|
||||||
|
theta_fc=0.2884
|
||||||
|
theta_wp=0.1086
|
||||||
|
current_date=start_date
|
||||||
|
qxData_tj = analyze_weather_data1(qx_data)
|
||||||
|
ke_data= {}
|
||||||
|
i=0
|
||||||
|
while current_date< monitor_date:
|
||||||
|
date_key = str(current_date.date())
|
||||||
|
u2=qxData_tj[date_key]['WIND_SPEED']['mean']
|
||||||
|
rain=qxData_tj[date_key]['RAIN_FALL_REALTIME']['max']
|
||||||
|
rh_min=qxData_tj[date_key]['AIR_HUMIDITY']['min']
|
||||||
|
if current_date == start_date:
|
||||||
|
DE_i_1 = 0
|
||||||
|
fw=0.35
|
||||||
|
else:
|
||||||
|
# 从上一天的数据中获取DE_i(如果存在)
|
||||||
|
prev_date = current_date - timedelta(days=1)
|
||||||
|
prev_date_key = str(prev_date.date())
|
||||||
|
if prev_date_key in ke_data:
|
||||||
|
DE_i_1 = ke_data[prev_date_key]['DE_i']
|
||||||
|
else:
|
||||||
|
DE_i_1 = 0 # 处理特殊情况(如start_date等于monitor_date)
|
||||||
|
if I > 0 and rain < 4:
|
||||||
|
fw = 0.35
|
||||||
|
elif I > 0 and rain < 4:
|
||||||
|
fw = 1
|
||||||
|
elif rain > 4 and I == 0:
|
||||||
|
fw = 1
|
||||||
|
else:
|
||||||
|
fw = ke_data[prev_date_key]['fw']
|
||||||
|
et0 = 0.0
|
||||||
|
kc = 0.0
|
||||||
|
if i < len(et0_list):
|
||||||
|
et0 = et0_list[i]
|
||||||
|
kc = kc_list[i]
|
||||||
|
ke,few=calculate_ke(hight, u2, rh_min, kc, DE_i_1, REW, fw, theta_fc,theta_wp)
|
||||||
|
et_soil=ke*et0
|
||||||
|
DE_i = calculate_D_ei(DE_i_1, rain, I,et_soil, fw,few)
|
||||||
|
#创建当天的数据字典
|
||||||
|
daily_data = {
|
||||||
|
'ke': ke,
|
||||||
|
'DE_i': DE_i,
|
||||||
|
'et0': et0,
|
||||||
|
'et_soil': et_soil,
|
||||||
|
'fw':fw,
|
||||||
|
"rain":rain,
|
||||||
|
"irrigation":I
|
||||||
|
}
|
||||||
|
ke_data[date_key]=daily_data
|
||||||
|
current_date = current_date + timedelta(days=1)
|
||||||
|
i=i+1
|
||||||
|
return ke_data
|
||||||
|
# 单天监测
|
||||||
|
def cal_irrigationByDay(zwlx_name,dkname,start_date,end_date,soil_key,weather_key,irrigation_really):
|
||||||
|
yesterday_date=(start_date - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||||
|
condition = {"Date": yesterday_date,"dkbm":dkname}
|
||||||
|
df=query_postgresql_to_dataframe(condition)
|
||||||
|
Di_1 = 0
|
||||||
|
if irrigation_really>0:
|
||||||
|
Di_1 = 0
|
||||||
|
update_irrigation_data(yesterday_date,dkname,"irrigation_really",irrigation_really)
|
||||||
|
else:
|
||||||
|
# 将Date列转换为日期时间类型
|
||||||
|
df['Date'] = pd.to_datetime(df['Date'])
|
||||||
|
if len(df[df.Date == (start_date - timedelta(days=1))])!=0:
|
||||||
|
isNeedjg=df[df.Date == (start_date- timedelta(days=1))]['isNeedirrigate'].values[0]
|
||||||
|
if isNeedjg:
|
||||||
|
Di_1 = df[df.Date == (start_date - timedelta(days=1))]['Di-1'].values[0]+df[df.Date == (start_date - timedelta(days=1))]['ETc'].values[0]
|
||||||
|
else:
|
||||||
|
Di_1 = df[df.Date == (start_date - timedelta(days=1))]['Di'].values[0]
|
||||||
|
dataframe=cal_dk_isNeegirrigate_Day(zwlx_name,dkname,soil_key,start_date,end_date,Di_1,weather_key)
|
||||||
|
condition_new = {"Date": start_date.strftime("%Y-%m-%d"), "dkbm": dkname}
|
||||||
|
df_query=query_postgresql_to_dataframe(condition_new)
|
||||||
|
if len(df_query)==0:
|
||||||
|
dataframe_to_postgresql_batch(dataframe)
|
||||||
|
else:
|
||||||
|
print("库表中已存在该数据")
|
||||||
|
return dataframe
|
||||||
|
if __name__ == "__main__":
|
||||||
|
#调用气象数据接口--获取具体日期的气象数据(气象)
|
||||||
|
# auth_token = loginAuth()
|
||||||
|
start_date = datetime(2025, 7, 16)
|
||||||
|
monitor_date = datetime(2025, 7, 17)
|
||||||
|
dk_name = "5"
|
||||||
|
zwlx_name="甘草"
|
||||||
|
soil_key = "8637b970-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
weather_key="18d121f0-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
irrigation_really=5
|
||||||
|
cal_irrigationByDay(zwlx_name,dk_name,start_date,monitor_date,soil_key,weather_key,irrigation_really)
|
||||||
|
# cal_dk_isNeegirrigate(dk_name, planting_date, monitor_date,0)
|
||||||
371
irrgiation/mathuntils.py
Normal file
371
irrgiation/mathuntils.py
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
#根据日期计算当年天数
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from matplotlib import pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
def is_leap_year(year):
|
||||||
|
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
|
||||||
|
|
||||||
|
def day_of_year(date_str):
|
||||||
|
date_part = date_str.split()[0]
|
||||||
|
year = int(date_str[:4]) # 取前4位 → 2025
|
||||||
|
month = int(date_str[5:7]) # 取第5-6位 → 06 → 6
|
||||||
|
day = int(date_str[8:10]) # 取第8-9位 → 30
|
||||||
|
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||||
|
if is_leap_year(year):
|
||||||
|
month_days[1] = 29 # 闰年2月29天
|
||||||
|
total = sum(month_days[:month-1]) + day
|
||||||
|
return total
|
||||||
|
#计算每天有效日照时长
|
||||||
|
def count_above_threshold(data, threshold,date):
|
||||||
|
filtered = filter(lambda x: float(x['value_cal']) >= threshold and x['datetime'].split(" ")[0] == date, data)
|
||||||
|
return len(list(filtered))
|
||||||
|
# 示例计算函数:Lux转W/m²
|
||||||
|
def lux_to_wm2(lux_value):
|
||||||
|
"""将Lux值转换为W/m²(使用标准系数0.0081)"""
|
||||||
|
return lux_value * 0.0081
|
||||||
|
def convert_lux_to_Radiant_Flux(data_dict):
|
||||||
|
"""
|
||||||
|
递归地将字典中的所有'timestamp'字段转换为可读的日期时间格式
|
||||||
|
保留原始时间戳的同时添加新的'datetime'字段
|
||||||
|
"""
|
||||||
|
if 'value' in data_dict:
|
||||||
|
radiant_Flux=data_dict["value"]*0.001496
|
||||||
|
data_dict["value_radiant"] =radiant_Flux
|
||||||
|
return data_dict
|
||||||
|
def lux_to_solar_rad(lux,h):
|
||||||
|
|
||||||
|
return lux * 0.001496*h * 3.6 / 1000
|
||||||
|
def plot(soil_data,name):
|
||||||
|
import matplotlib.dates as mdates
|
||||||
|
# 创建画布
|
||||||
|
plt.figure(figsize=(14, 8))
|
||||||
|
|
||||||
|
# 处理并绘制地表湿度数据
|
||||||
|
surface_df = pd.DataFrame(soil_data["SOIL_MOISTURE_SURFACE"])
|
||||||
|
surface_df['datetime'] = pd.to_datetime(surface_df['datetime'])
|
||||||
|
surface_df['value'] = surface_df['value'].astype(float)
|
||||||
|
plt.plot(surface_df['datetime'], surface_df['value'],
|
||||||
|
label='Surface Moisture (0-5cm)', color='#FF6B6B', linewidth=2, marker='o', markersize=5)
|
||||||
|
|
||||||
|
# 处理并绘制中层湿度数据
|
||||||
|
middle_df = pd.DataFrame(soil_data["SOIL_MOISTURE_MIDDLE"])
|
||||||
|
middle_df['datetime'] = pd.to_datetime(middle_df['datetime'])
|
||||||
|
middle_df['value'] = middle_df['value'].astype(float)
|
||||||
|
plt.plot(middle_df['datetime'], middle_df['value'],
|
||||||
|
label='Middle Layer Moisture', color='#4ECDC4', linewidth=2, marker='s', markersize=5)
|
||||||
|
# 处理并绘制中层湿度数据
|
||||||
|
middle_df = pd.DataFrame(soil_data["SOIL_MOISTURE_BOTTOM"])
|
||||||
|
middle_df['datetime'] = pd.to_datetime(middle_df['datetime'])
|
||||||
|
middle_df['value'] = middle_df['value'].astype(float)
|
||||||
|
plt.plot(middle_df['datetime'], middle_df['value'],
|
||||||
|
label='BOTTOM Layer Moisture', color='blue', linewidth=2, marker='s', markersize=5)
|
||||||
|
|
||||||
|
# 设置图表格式
|
||||||
|
plt.title('Soil Moisture Variation (Jun 18 - Jul 3, 2025)', fontsize=16, pad=20)
|
||||||
|
plt.xlabel('Date', fontsize=12)
|
||||||
|
plt.ylabel('Soil Moisture (%)', fontsize=12)
|
||||||
|
plt.grid(True, linestyle='--', alpha=0.7)
|
||||||
|
|
||||||
|
# 设置x轴日期格式
|
||||||
|
ax = plt.gca()
|
||||||
|
ax.xaxis.set_major_locator(mdates.DayLocator(interval=2))
|
||||||
|
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
|
||||||
|
plt.xticks(rotation=45)
|
||||||
|
|
||||||
|
# 添加图例和注释
|
||||||
|
plt.legend(loc='upper right', fontsize=10)
|
||||||
|
plt.tight_layout()
|
||||||
|
# 保存图片到本地
|
||||||
|
output_path = 'D:\悦柠\遥感\甘草\ceshi/'+name+".png"
|
||||||
|
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
||||||
|
# 显示图表
|
||||||
|
# plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def rh_to_theta_vg(rh, soil_params, temperature=25):
|
||||||
|
"""使用Van Genuchten模型将相对湿度转换为体积含水量"""
|
||||||
|
# 计算土壤水势(kPa)
|
||||||
|
psi = -145 * np.log(rh / 100)
|
||||||
|
|
||||||
|
# 提取土壤参数
|
||||||
|
theta_s = soil_params["theta_s"] # 饱和含水量
|
||||||
|
theta_r = soil_params["theta_r"] # 残余含水量
|
||||||
|
alpha = soil_params["alpha"] # Van Genuchten参数
|
||||||
|
n = soil_params["n"] # Van Genuchten参数
|
||||||
|
m = 1 - 1 / n
|
||||||
|
|
||||||
|
# 计算体积含水量
|
||||||
|
theta = theta_r + (theta_s - theta_r) / ((1 + (alpha * abs(psi)) ** n) ** m)
|
||||||
|
print(theta)
|
||||||
|
return theta
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_soil_moisture_after_irrigation(
|
||||||
|
initial_moisture: float,
|
||||||
|
field_capacity: float,
|
||||||
|
irrigation_volume: float,
|
||||||
|
soil_volume: float,
|
||||||
|
area: float = None,
|
||||||
|
verbose: bool = False
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
不考虑降雨量和蒸散量时,计算灌溉后的土壤相对湿度
|
||||||
|
|
||||||
|
参数:
|
||||||
|
initial_moisture: 初始土壤相对湿度 (%)
|
||||||
|
field_capacity: 田间持水量 (%)
|
||||||
|
irrigation_volume: 灌溉量 (m³)
|
||||||
|
soil_volume: 土壤体积 (m³)
|
||||||
|
area: 灌溉面积 (㎡,可选,仅用于打印提示)
|
||||||
|
verbose: 是否打印详细计算过程
|
||||||
|
|
||||||
|
返回:
|
||||||
|
float: 灌溉后土壤相对湿度 (%)
|
||||||
|
"""
|
||||||
|
# 参数验证
|
||||||
|
if not (0 <= initial_moisture <= 100):
|
||||||
|
raise ValueError("初始湿度必须在0-100%之间")
|
||||||
|
if not (0 < field_capacity <= 100):
|
||||||
|
raise ValueError("田间持水量必须在0-100%之间且大于0")
|
||||||
|
if irrigation_volume < 0:
|
||||||
|
raise ValueError("灌溉量不能为负数")
|
||||||
|
if soil_volume <= 0:
|
||||||
|
raise ValueError("土壤体积必须大于0")
|
||||||
|
|
||||||
|
# 计算初始土壤含水量(m³)
|
||||||
|
initial_water = (initial_moisture / 100) * (field_capacity / 100) * soil_volume
|
||||||
|
|
||||||
|
# 灌溉后总含水量(m³)
|
||||||
|
total_water = initial_water + irrigation_volume
|
||||||
|
|
||||||
|
# 计算田间持水总量(m³)
|
||||||
|
max_water = (field_capacity / 100) * soil_volume
|
||||||
|
|
||||||
|
# 计算灌溉后湿度(超过田间持水量时取100%)
|
||||||
|
final_moisture = min((total_water / max_water) * 100, 100.0)
|
||||||
|
|
||||||
|
# if verbose:
|
||||||
|
# area_info = f"(面积: {area}㎡)" if area else ""
|
||||||
|
# print(f"===== 不考虑降雨与蒸散的灌溉计算 {area_info} =====")
|
||||||
|
# print(f"初始湿度: {initial_moisture}%")
|
||||||
|
# print(f"田间持水量: {field_capacity}%")
|
||||||
|
# print(f"土壤体积: {soil_volume}m³")
|
||||||
|
# print(f"灌溉量: {irrigation_volume}m³")
|
||||||
|
# print(f"初始含水量: {initial_water:.2f}m³")
|
||||||
|
# print(f"灌溉后含水量: {total_water:.2f}m³")
|
||||||
|
# print(f"田间最大持水量: {max_water:.2f}m³")
|
||||||
|
# print(f"灌溉后土壤湿度: {final_moisture:.2f}%")
|
||||||
|
|
||||||
|
return final_moisture
|
||||||
|
def calculate_irrigation_volume(
|
||||||
|
initial_moisture: float,
|
||||||
|
target_moisture: float,
|
||||||
|
field_capacity: float,
|
||||||
|
soil_volume: float
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算为达到目标土壤相对湿度所需的灌溉量
|
||||||
|
|
||||||
|
参数:
|
||||||
|
initial_moisture: 初始土壤相对湿度 (%)
|
||||||
|
target_moisture: 目标土壤相对湿度 (%)
|
||||||
|
field_capacity: 田间持水量 (%)
|
||||||
|
soil_volume: 土壤体积 (m³)
|
||||||
|
area: 灌溉面积 (㎡,可选,仅用于打印提示)
|
||||||
|
verbose: 是否打印详细计算过程
|
||||||
|
|
||||||
|
返回:
|
||||||
|
float: 需要的灌溉量 (m³)
|
||||||
|
"""
|
||||||
|
# 参数验证
|
||||||
|
if not (0 <= initial_moisture <= 100):
|
||||||
|
raise ValueError("初始湿度必须在0-100%之间")
|
||||||
|
if not (0 <= target_moisture <= 100):
|
||||||
|
raise ValueError("目标湿度必须在0-100%之间")
|
||||||
|
if not (0 < field_capacity <= 100):
|
||||||
|
raise ValueError("田间持水量必须在0-100%之间且大于0")
|
||||||
|
if soil_volume <= 0:
|
||||||
|
raise ValueError("土壤体积必须大于0")
|
||||||
|
if target_moisture <= initial_moisture:
|
||||||
|
raise ValueError("目标湿度必须大于初始湿度")
|
||||||
|
|
||||||
|
# 计算初始土壤含水量(m³)
|
||||||
|
initial_water = (initial_moisture / 100) * (field_capacity / 100) * soil_volume
|
||||||
|
|
||||||
|
# 计算目标含水量(m³)
|
||||||
|
target_water = (target_moisture / 100) * (field_capacity / 100) * soil_volume
|
||||||
|
|
||||||
|
# 需要的灌溉量
|
||||||
|
irrigation_volume = target_water - initial_water
|
||||||
|
|
||||||
|
return irrigation_volume
|
||||||
|
def irriDepth_irrvolume(area,irrDepth):
|
||||||
|
volume=area*667*(irrDepth/1000)
|
||||||
|
return volume
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_irrigation(dry_soil_weight, current_humidity, target_humidity,
|
||||||
|
irrigation_efficiency=0.8, water_density=1000):
|
||||||
|
"""
|
||||||
|
计算达到目标湿度所需的灌溉量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dry_soil_weight (float): 土壤干重(kg)
|
||||||
|
current_humidity (float): 当前相对湿度(%)
|
||||||
|
target_humidity (float): 目标相对湿度(%)
|
||||||
|
irrigation_efficiency (float): 灌溉效率(0~1)
|
||||||
|
water_density (float): 水密度(kg/m³)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 灌溉量(立方米)
|
||||||
|
"""
|
||||||
|
if current_humidity >= target_humidity:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
delta_humidity = (target_humidity - current_humidity) / 100
|
||||||
|
water_weight = dry_soil_weight * delta_humidity
|
||||||
|
water_volume = water_weight / water_density
|
||||||
|
return water_volume / irrigation_efficiency
|
||||||
|
def calculate_rh_target(
|
||||||
|
initial_moisture: float,
|
||||||
|
root: float,
|
||||||
|
field_capacity: float,
|
||||||
|
irrigation:float
|
||||||
|
) -> float:
|
||||||
|
initial_moisture=(initial_moisture / 100) * field_capacity
|
||||||
|
iir_volume=irrigation/(10*root)
|
||||||
|
target_moisture = initial_moisture + (iir_volume/field_capacity)
|
||||||
|
return target_moisture
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_rh_target(
|
||||||
|
initial_moisture: float, # 初始土壤相对湿度(%)
|
||||||
|
root: float, # 根系深度(cm)
|
||||||
|
field_capacity: float, # 田间持水量(体积含水量 cm³/cm³)
|
||||||
|
irrigation: float # 灌溉量(mm)
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算灌溉后的目标土壤相对湿度(%)
|
||||||
|
|
||||||
|
参数:
|
||||||
|
initial_moisture: 初始土壤相对湿度(%)
|
||||||
|
root: 根系深度(cm)
|
||||||
|
field_capacity: 田间持水量(体积含水量 cm³/cm³)
|
||||||
|
irrigation: 灌溉量(mm)
|
||||||
|
|
||||||
|
返回:
|
||||||
|
float: 灌溉后的目标土壤相对湿度(%)
|
||||||
|
"""
|
||||||
|
# 参数验证
|
||||||
|
if initial_moisture < 0 or initial_moisture > 100:
|
||||||
|
raise ValueError("初始湿度必须在0-100%之间")
|
||||||
|
if root <= 0:
|
||||||
|
raise ValueError("根系深度必须大于0")
|
||||||
|
if field_capacity <= 0 or field_capacity > 1:
|
||||||
|
raise ValueError("田间持水量必须在0-1 cm³/cm³之间")
|
||||||
|
if irrigation < 0:
|
||||||
|
raise ValueError("灌溉量不能为负值")
|
||||||
|
|
||||||
|
# 将相对湿度转为体积含水量
|
||||||
|
initial_volume_moisture = (initial_moisture / 100) * field_capacity
|
||||||
|
|
||||||
|
# 计算灌溉增加的体积含水量
|
||||||
|
# 将mm转为cm,并除以根系深度得到体积含水量增量
|
||||||
|
moisture_increase = (irrigation / 10) / root
|
||||||
|
|
||||||
|
# 计算灌溉后的体积含水量
|
||||||
|
target_volume_moisture = initial_volume_moisture + moisture_increase
|
||||||
|
# 转换回相对湿度(%)
|
||||||
|
target_rh = (target_volume_moisture / field_capacity) * 100
|
||||||
|
|
||||||
|
# 确保不超过100%
|
||||||
|
target_rh = min(target_rh, 100.0)
|
||||||
|
|
||||||
|
# # 3. 计算目标体积含水量 (不超过田间持水量)
|
||||||
|
# target_volume = min(initial_volume + moisture_increase, field_capacity)
|
||||||
|
#
|
||||||
|
# # 4. 转换回相对湿度 (%)
|
||||||
|
# target_rh = (target_volume / field_capacity) * 100
|
||||||
|
|
||||||
|
return target_rh
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_rh_target_rz(
|
||||||
|
initial_moisture: float, # 初始土壤相对湿度(%)
|
||||||
|
soil_root: float, # 根系深度(cm)
|
||||||
|
area: float, # 灌溉面积(亩)
|
||||||
|
irrigation: float, # 灌溉量(mm)
|
||||||
|
field_capacity: float = 28.84, # 田间持水量(%),默认35%
|
||||||
|
soil_bulk_density: float = 1.5 # 土壤容重(g/cm³),默认1.5
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算灌溉后的目标根区土壤相对湿度
|
||||||
|
|
||||||
|
参数:
|
||||||
|
initial_moisture: 初始土壤相对湿度(%)
|
||||||
|
soil_root: 根系深度(cm)
|
||||||
|
area: 灌溉面积(亩)
|
||||||
|
irrigation: 灌溉量(mm)
|
||||||
|
field_capacity: 田间持水量(%),默认35%
|
||||||
|
soil_bulk_density: 土壤容重(g/cm³),默认1.5
|
||||||
|
|
||||||
|
返回:
|
||||||
|
float: 灌溉后的目标土壤相对湿度(%)
|
||||||
|
"""
|
||||||
|
# 参数验证
|
||||||
|
if initial_moisture < 0 or initial_moisture > 100:
|
||||||
|
raise ValueError("初始湿度必须在0-100%之间")
|
||||||
|
if soil_root <= 0:
|
||||||
|
raise ValueError("根系深度必须大于0")
|
||||||
|
if area <= 0:
|
||||||
|
raise ValueError("面积必须大于0")
|
||||||
|
if irrigation < 0:
|
||||||
|
raise ValueError("灌溉量不能为负值")
|
||||||
|
|
||||||
|
# 计算根区体积
|
||||||
|
root_volume = area * 667 * (soil_root / 100) # m³,将cm转为m
|
||||||
|
|
||||||
|
# 计算灌溉水体积
|
||||||
|
water_volume = (irrigation / 1000) * area * 667 # m³,将mm转为m
|
||||||
|
|
||||||
|
# 计算灌溉增加的土壤湿度(%)
|
||||||
|
moisture_increase = (water_volume / (root_volume * soil_bulk_density)) * 100
|
||||||
|
|
||||||
|
# 计算目标湿度
|
||||||
|
target_moisture = initial_moisture + moisture_increase
|
||||||
|
|
||||||
|
# 确保不超过田间持水量
|
||||||
|
target_moisture = min(target_moisture, field_capacity)
|
||||||
|
|
||||||
|
return target_moisture
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# volume=irriDepth_irrvolume(10.95,11.123)
|
||||||
|
# soil_volume_dk1 = 10.95 * 667 * 0.15
|
||||||
|
# soil_volume_dk2 = 8.92 * 667 * 0.15
|
||||||
|
# result1 = calculate_soil_moisture_after_irrigation(
|
||||||
|
# initial_moisture=15.2,
|
||||||
|
# field_capacity=28.84,
|
||||||
|
# irrigation_volume=168,
|
||||||
|
# soil_volume=soil_volume_dk1,
|
||||||
|
# verbose=True
|
||||||
|
# )
|
||||||
|
# print(f"\n示例1结果: 灌溉后湿度 = {result1:.2f}%\n")
|
||||||
|
# print(soil_volume_dk1)
|
||||||
|
# result2=calculate_irrigation_volume(initial_moisture=15.44,
|
||||||
|
# field_capacity=28.84,
|
||||||
|
# target_moisture=70,
|
||||||
|
# soil_volume=soil_volume_dk1)
|
||||||
|
# print(f"\n达到目标湿度所需灌溉量 = {result2:.2f}\n")
|
||||||
|
# # 调用示例
|
||||||
|
# dry_soil_weight=10.95*667*0.1*1200
|
||||||
|
# volume = calculate_irrigation(
|
||||||
|
# dry_soil_weight=dry_soil_weight,
|
||||||
|
# current_humidity=15.4,
|
||||||
|
# target_humidity=70
|
||||||
|
# )
|
||||||
|
# print(f"灌溉量: {volume:.2f} m³") # 输出: 0.09 m³
|
||||||
|
rh=calculate_rh_target(67.96,6,0.2884,33.14)
|
||||||
|
rh1 = rh*0.7
|
||||||
|
print(rh,rh1)
|
||||||
133
irrgiation/soil_Ke.py
Normal file
133
irrgiation/soil_Ke.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# 计算土壤蒸发系数
|
||||||
|
def calculate_ke(h, u2, rh_min, k_cb, DE_i_1,REW,fw,theta_fc,theta_wp):
|
||||||
|
"""
|
||||||
|
计算土壤蒸发系数 ke
|
||||||
|
参数:
|
||||||
|
h: 株高(m)
|
||||||
|
u2: 2米高处风速(m/s)
|
||||||
|
rh_min: 最小相对湿度(%)
|
||||||
|
k_cb: 基础作物系数
|
||||||
|
DE_i_1: 前一天从土壤表层蒸发的累积深度(mm)
|
||||||
|
TEW: 总可蒸发水量(mm)
|
||||||
|
REW: 易蒸发水量(mm)
|
||||||
|
few: 裸露湿润的土壤的比值
|
||||||
|
返回:
|
||||||
|
ke: 土壤蒸发系数
|
||||||
|
"""
|
||||||
|
K_cmin = 0.175
|
||||||
|
Z_e=0.12
|
||||||
|
kc_max = calculate_kc_max(h, u2, rh_min, k_cb)
|
||||||
|
fc = calculate_fc(k_cb, K_cmin, kc_max, h)
|
||||||
|
few = calculate_fev(fc, fw)
|
||||||
|
TEW = calculate_tew(theta_fc, theta_wp, Z_e)
|
||||||
|
kr = calculate_Kr(DE_i_1, TEW, REW)
|
||||||
|
term1 = kr * (kc_max - k_cb)
|
||||||
|
term2 = few * k_cb
|
||||||
|
return min(term1, term2),few
|
||||||
|
|
||||||
|
# 每日土壤蒸发量累积量
|
||||||
|
#土壤蒸发有效部分
|
||||||
|
def calculate_fev(fc, fw):
|
||||||
|
fev = min(1 - fc, fw)
|
||||||
|
return fev
|
||||||
|
#计算作物覆盖率fc
|
||||||
|
def calculate_fc(Kcb, Kcmin, Kcmax, h):
|
||||||
|
#Kcb:作物基础系数,Kcmin:无地表覆盖的干燥土壤最小 值(约为 0.15-0.20),Kcmax:紧随湿润过程的土壤最大 值
|
||||||
|
|
||||||
|
fc = ((Kcb- Kcmin) / (Kcmax - Kcmin)) ** (1 + 0.5 * h)
|
||||||
|
return fc
|
||||||
|
# 计算蒸发减少系数
|
||||||
|
def calculate_Kr(DE_i_1, TEW, REW):
|
||||||
|
if DE_i_1 <= REW:
|
||||||
|
Kr = 1
|
||||||
|
else:
|
||||||
|
print("dd")
|
||||||
|
Kr = (TEW - DE_i_1) / (TEW - REW)
|
||||||
|
return Kr
|
||||||
|
#计算Kcmax
|
||||||
|
def calculate_kc_max(h: float, u2: float, rh_min: float, k_cb: float) -> float:
|
||||||
|
"""
|
||||||
|
计算作物系数上限 kc_max
|
||||||
|
参数:
|
||||||
|
h: 株高(m)
|
||||||
|
u2: 2米高处风速(m/s)
|
||||||
|
rh_min: 最小相对湿度(%)
|
||||||
|
k_cb: 基础作物系数
|
||||||
|
返回:
|
||||||
|
kc_max: 作物系数上限
|
||||||
|
"""
|
||||||
|
term1 = 1.2 + (0.04 * (u2 - 2) - 0.004 * (rh_min - 45)) * (h / 3) ** 0.3
|
||||||
|
term2 = k_cb + 0.5
|
||||||
|
return max(term1, term2)
|
||||||
|
#计算TEW
|
||||||
|
def calculate_tew(theta_fc: float, theta_wp: float, Z_e: float) -> float:
|
||||||
|
"""
|
||||||
|
计算总可蒸发水量 TEW
|
||||||
|
参数:
|
||||||
|
theta_fc: 田间持水量(m³/m³)
|
||||||
|
theta_wp: 萎蔫系数(m³/m³)
|
||||||
|
Z_e: 土壤蒸发层深度(cm)
|
||||||
|
返回:
|
||||||
|
TEW: 总可蒸发水量(mm)
|
||||||
|
"""
|
||||||
|
return 1000 * (theta_fc - theta_wp) * Z_e
|
||||||
|
# 计算深层渗漏损失
|
||||||
|
def calculate_DP_ei(P_i, RO_i, I_i, D_e_prev, f_w):
|
||||||
|
if (P_i - RO_i) + I_i / f_w - D_e_prev >= 0:
|
||||||
|
return (P_i - RO_i) + I_i / f_w - D_e_prev
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 计算每日土壤蒸发量累积量
|
||||||
|
def calculate_D_ei(D_e_prev, P_i, I_i, E_i, f_w, f_ew):
|
||||||
|
RO_i=0
|
||||||
|
T_ew_i=0
|
||||||
|
DP_ei = calculate_DP_ei(P_i, RO_i, I_i, D_e_prev, f_w)
|
||||||
|
D_ei = D_e_prev - (P_i - RO_i) - I_i / f_w + E_i / f_ew + T_ew_i + DP_ei
|
||||||
|
return D_ei
|
||||||
|
# 示例计算
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# # 示例参数
|
||||||
|
# h = 0.07 # 株高(m)
|
||||||
|
# u2 = 3.0 # 2米高处风速(m/s)
|
||||||
|
# rh_min = 20.0 # 最小相对湿度(%)
|
||||||
|
# k_cb = 0.4 # 基础作物系数
|
||||||
|
# K_s = 0.6 # 土壤导水率
|
||||||
|
# K_cmin = 0.175
|
||||||
|
#
|
||||||
|
# fw = 0.35 # 土壤表层被灌溉浸湿的部分
|
||||||
|
# DE_i = 6.24 # 前一天从土壤表层蒸发的累积深度(mm)
|
||||||
|
# DE_i_1 = 2.965 # 前一天从土壤表层蒸发的累积深度(mm)
|
||||||
|
# theta_fc = 0.2884 # 田间持水量(m³/m³)
|
||||||
|
# theta_wp = 0.1086 # 萎蔫系数(m³/m³)
|
||||||
|
# Z_e = 0.1 # 土壤蒸发层深度(m)
|
||||||
|
#
|
||||||
|
# # # # 计算 TEW
|
||||||
|
# # TEW = calculate_tew(theta_fc, theta_wp, Z_e)
|
||||||
|
# REW = 5.178 # 假设REW为TEW的40%
|
||||||
|
# # #
|
||||||
|
# # # # 计算各参数
|
||||||
|
# # kc_max = calculate_kc_max(h, u2, rh_min, k_cb)
|
||||||
|
# # fc = calculate_fc(k_cb, K_cmin, kc_max, h)
|
||||||
|
# # few= calculate_fev(fc, fw)
|
||||||
|
# # kr = calculate_Kr(DE_i_1,DE_i, TEW, REW)
|
||||||
|
# ke=calculate_ke(h, u2, rh_min, k_cb, DE_i_1,DE_i,REW)
|
||||||
|
# # 输出结果
|
||||||
|
#
|
||||||
|
# print(f"土壤蒸发系数 Ke: {ke:.4f}")
|
||||||
|
# 假设初始参数
|
||||||
|
TEW = 1000 # 总可蒸发水量,单位:mm
|
||||||
|
D_e_start = TEW # 模拟开始时的初始D_e
|
||||||
|
D_e_prev = 0 # 模拟第一天的D_e,假设充分灌溉后为0
|
||||||
|
# 假设一些每日数据,实际应用中需替换为真实数据
|
||||||
|
P_i = 10 # 第i天的降雨量,单位:mm
|
||||||
|
RO_i = 0 # 第i天的降雨形成地表径流量,这里假定为0,单位:mm
|
||||||
|
I_i = 19 # 第i天渗入土壤的灌溉量,单位:mm
|
||||||
|
k_e = 0.14 # 土壤蒸发系数
|
||||||
|
ET_o = 8.188 # 参考作物蒸散量,单位:mm
|
||||||
|
E_i = k_e * ET_o # 第i天的蒸发量
|
||||||
|
T_ew_i = 0 # 第i天湿润裸露表层土壤的蒸腾量,这里假定为0,单位:mm
|
||||||
|
f_w = 0.8 # 经灌溉湿润的土壤面积比值
|
||||||
|
f_ew = 0.6 # 裸露湿润土壤面积比值
|
||||||
|
D_ei = calculate_D_ei(D_e_prev, P_i, RO_i, I_i, f_w, f_ew)
|
||||||
|
print(f"第i天末土壤完全湿润后的累积蒸发(消耗)量为: {D_ei} mm")
|
||||||
10
irrgiation/utils.py
Normal file
10
irrgiation/utils.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
dk_irriationInfo={
|
||||||
|
"1号地":{
|
||||||
|
"dkbm":1,
|
||||||
|
"irriationFile":"D:/悦柠/遥感/甘草/model_result/1号地_灌溉数据.xlsx"
|
||||||
|
},
|
||||||
|
"5号地":{
|
||||||
|
"dkbm":5,
|
||||||
|
"irriationFile":"D:/悦柠/遥感/甘草/model_result/5号地_灌溉数据.xlsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
336
irrgiation/weatherAndSoilDataRequest.py
Normal file
336
irrgiation/weatherAndSoilDataRequest.py
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
from datetime import datetime, timezone, timedelta, date
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
from matplotlib import pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
#登录接口认证
|
||||||
|
|
||||||
|
def loginAuth():
|
||||||
|
# 接口地址
|
||||||
|
global auth_token
|
||||||
|
url = "https://xj-tb.maimaiag.com/api/auth/login" # 请替换为实际地址
|
||||||
|
|
||||||
|
# 请求头
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 请求体数据
|
||||||
|
payload = {
|
||||||
|
"username": "read@xj.com",
|
||||||
|
"password": "maimai"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送POST请求
|
||||||
|
response = requests.post(
|
||||||
|
url,
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(payload) # 或者直接使用 json=payload
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查响应状态码
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("登录成功!")
|
||||||
|
print("响应数据:", response.json())
|
||||||
|
|
||||||
|
# 通常登录接口会返回token,可以这样获取
|
||||||
|
response_data = response.json()
|
||||||
|
auth_token = response_data.get("token") # 根据实际返回字段调整
|
||||||
|
if auth_token:
|
||||||
|
print("获取到的认证Token:", auth_token)
|
||||||
|
else:
|
||||||
|
print(f"登录失败,状态码: {response.status_code}")
|
||||||
|
print("错误信息:", response.text)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"请求发生异常: {str(e)}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"JSON解析错误: {str(e)}")
|
||||||
|
return auth_token
|
||||||
|
# 调用数据接口--历史数据
|
||||||
|
def getWeatherAndSoilData(auth_token,device_id,startTs,endTs,agg,interval,limit,orderBy,keys):
|
||||||
|
# 配置信息
|
||||||
|
BASE_URL = "https://xj-tb.maimaiag.com/" # 请替换为实际API地址
|
||||||
|
TOKEN = auth_token # 替换为实际的Bearer token
|
||||||
|
DEVICE_ID = device_id # 替换为实际的设备ID
|
||||||
|
# 构造请求URL
|
||||||
|
endpoint = f"/api/plugins/telemetry/DEVICE/{DEVICE_ID}/values/timeseries"
|
||||||
|
url = BASE_URL + endpoint
|
||||||
|
# 请求头
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Authorization": f"Bearer+{TOKEN}"
|
||||||
|
}
|
||||||
|
# 查询参数
|
||||||
|
params = {
|
||||||
|
"keys": keys, # 要查询的参数,多个用逗号分隔
|
||||||
|
"startTs": startTs, # 开始时间戳(毫秒)- 可选
|
||||||
|
"endTs": endTs, # 结束时间戳(毫秒)- 可选
|
||||||
|
"agg": agg, # 聚合函数 - 可选
|
||||||
|
"interval": interval, # 聚合间隔(毫秒)- 可选
|
||||||
|
"limit": limit, # 返回条数 - 可选
|
||||||
|
"orderBy": orderBy # 排序方式 - 可选
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
# 发送GET请求
|
||||||
|
response = requests.get(
|
||||||
|
url,
|
||||||
|
headers=headers,
|
||||||
|
params=params
|
||||||
|
)
|
||||||
|
# 检查响应状态码
|
||||||
|
if response.status_code == 200:
|
||||||
|
# print("请求成功!")
|
||||||
|
data = response.json()
|
||||||
|
# print("响应数据:", json.dumps(data, indent=2))
|
||||||
|
converted_data = {k: convert_timestamp_to_datetime(v) for k, v in data.items()}
|
||||||
|
return converted_data
|
||||||
|
elif response.status_code == 401:
|
||||||
|
print("认证失败,请检查Token是否正确")
|
||||||
|
elif response.status_code == 404:
|
||||||
|
print("设备不存在或路径错误")
|
||||||
|
else:
|
||||||
|
print(f"请求失败,状态码: {response.status_code}")
|
||||||
|
print("错误信息:", response.text)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"请求发生异常: {str(e)}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"JSON解析错误: {str(e)}")
|
||||||
|
print("原始响应:", response.text)
|
||||||
|
def convert_timestamp_to_datetime(data_dict):
|
||||||
|
"""
|
||||||
|
递归地将字典中的所有'timestamp'字段转换为可读的日期时间格式
|
||||||
|
保留原始时间戳的同时添加新的'datetime'字段
|
||||||
|
"""
|
||||||
|
if isinstance(data_dict, dict):
|
||||||
|
if "ts" in data_dict:
|
||||||
|
# 转换毫秒时间戳为datetime对象
|
||||||
|
timestamp_sec = data_dict["ts"] / 1000
|
||||||
|
dt = datetime.fromtimestamp(timestamp_sec)
|
||||||
|
# 添加新字段,保留原始时间戳
|
||||||
|
data_dict["datetime"] = dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
return data_dict
|
||||||
|
elif isinstance(data_dict, list):
|
||||||
|
return [convert_timestamp_to_datetime(item) for item in data_dict]
|
||||||
|
else:
|
||||||
|
return data_dict
|
||||||
|
|
||||||
|
def dateToTimestamp(date):
|
||||||
|
# dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
|
||||||
|
if isinstance(date, datetime):
|
||||||
|
# 如果已经是datetime对象,直接使用
|
||||||
|
dt = date
|
||||||
|
elif isinstance(date, str):
|
||||||
|
# 如果是字符串,解析为datetime对象
|
||||||
|
dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S.%f")
|
||||||
|
else:
|
||||||
|
raise TypeError("date参数必须是字符串或datetime对象")
|
||||||
|
timestamp_ms = int(dt.timestamp() * 1000)
|
||||||
|
return timestamp_ms
|
||||||
|
|
||||||
|
#调用接口获取未来气象数据
|
||||||
|
def getFutureWeather(monitor_date):
|
||||||
|
base_url_future="https://api.open-meteo.com/"
|
||||||
|
end_point="v1/forecast"
|
||||||
|
current_date = datetime.now().date()
|
||||||
|
params = {
|
||||||
|
"latitude": "41.34252110189814", # 纬度
|
||||||
|
"longitude": "86.28810755462379", # 经度
|
||||||
|
"hourly": "temperature_2m,precipitation,precipitation_probability"
|
||||||
|
}
|
||||||
|
# if monitor_date.date()<current_date:
|
||||||
|
# # url = base_url_history + end_point
|
||||||
|
# params["start_date"] = (monitor_date + timedelta(days=1)).strftime("%Y-%m-%d")
|
||||||
|
# params["end_date"] = (monitor_date + timedelta(days=3)).strftime("%Y-%m-%d")
|
||||||
|
#
|
||||||
|
# else:
|
||||||
|
url = base_url_future + end_point
|
||||||
|
params["forecast_days"]= 4
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送GET请求
|
||||||
|
response = requests.get(
|
||||||
|
url,
|
||||||
|
params=params
|
||||||
|
)
|
||||||
|
# 检查响应状态码
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
result={}
|
||||||
|
if monitor_date.date()<current_date:
|
||||||
|
result["qw_day1"] = get_tjvalue(data['hourly']['temperature_2m'][:24])
|
||||||
|
result["qw_day2"] = get_tjvalue(data['hourly']['temperature_2m'][24:48])
|
||||||
|
result["qw_day3"] = get_tjvalue(data['hourly']['temperature_2m'][48:72])
|
||||||
|
result["js_day1"] = get_tjvalue(data['hourly']['precipitation'][:24])
|
||||||
|
result["js_day2"] = get_tjvalue(data['hourly']['precipitation'][24:48])
|
||||||
|
result["js_day3"] = get_tjvalue(data['hourly']['precipitation'][48:72])
|
||||||
|
else:
|
||||||
|
result["qw_day1"] = get_tjvalue(data['hourly']['temperature_2m'][24:48])
|
||||||
|
result["qw_day2"] = get_tjvalue(data['hourly']['temperature_2m'][48:72])
|
||||||
|
result["qw_day3"] = get_tjvalue(data['hourly']['temperature_2m'][72:96])
|
||||||
|
result["js_day1"] = get_tjvalue(data['hourly']['precipitation'][24:48])
|
||||||
|
result["js_day2"] = get_tjvalue(data['hourly']['precipitation'][48:72])
|
||||||
|
result["js_day3"] = get_tjvalue(data['hourly']['precipitation'][72:96])
|
||||||
|
return result
|
||||||
|
elif response.status_code == 401:
|
||||||
|
print("认证失败,请检查Token是否正确")
|
||||||
|
elif response.status_code == 404:
|
||||||
|
print("设备不存在或路径错误")
|
||||||
|
else:
|
||||||
|
print(f"请求失败,状态码: {response.status_code}")
|
||||||
|
print("错误信息:", response.text)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"请求发生异常: {str(e)}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"JSON解析错误: {str(e)}")
|
||||||
|
print("原始响应:", response.text)
|
||||||
|
def get_tjvalue(data):
|
||||||
|
result={}
|
||||||
|
max_val = max(data)
|
||||||
|
min_val = min(data)
|
||||||
|
mean_val = sum(data) / len(data)
|
||||||
|
sum_val = sum(data)
|
||||||
|
result['max']=max_val
|
||||||
|
result['min']=min_val
|
||||||
|
result['mean']=mean_val
|
||||||
|
result['sum'] = sum_val
|
||||||
|
return result
|
||||||
|
soil_params={
|
||||||
|
"1号传感器":{
|
||||||
|
"device_id":"31fbb2d0-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"2号传感器": {
|
||||||
|
"device_id": "3bf4cc90-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"3号传感器": {
|
||||||
|
"device_id": "43a57cf0-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"4号传感器": {
|
||||||
|
"device_id": "4ff6b780-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"5号传感器": {
|
||||||
|
"device_id": "598c5480-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"6号传感器": {
|
||||||
|
"device_id": "6ff07260-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"7号传感器": {
|
||||||
|
"device_id": "8637b970-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"8号传感器": {
|
||||||
|
"device_id": "955de5f0-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"9号传感器": {
|
||||||
|
"device_id": "a24e94d0-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"10号传感器": {
|
||||||
|
"device_id": "adb7e060-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"11号传感器": {
|
||||||
|
"device_id": "b74c6bf0-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
"12号传感器": {
|
||||||
|
"device_id": "c0f36e10-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
def get_soil_data_dk1(auth_token,startTs, endTs):
|
||||||
|
keys_soil = "SOIL_MOISTURE_SURFACE,SOIL_MOISTURE_MIDDLE,SOIL_MOISTURE_BOTTOM"
|
||||||
|
limit = ""
|
||||||
|
orderBy = ""
|
||||||
|
result={}
|
||||||
|
for key in soil_params:
|
||||||
|
id=soil_params[key]['device_id']
|
||||||
|
soil_data = getWeatherAndSoilData(auth_token,id, startTs, endTs, "AVG", 3600000, limit, orderBy,
|
||||||
|
keys_soil)
|
||||||
|
result[key]=soil_data
|
||||||
|
return result
|
||||||
|
def get_soil_data_dk2(auth_token,device_id,startTs, endTs):
|
||||||
|
keys_soil = "SOIL_MOISTURE_SURFACE,SOIL_MOISTURE_MIDDLE,SOIL_MOISTURE_BOTTOM"
|
||||||
|
limit = ""
|
||||||
|
orderBy = ""
|
||||||
|
result={}
|
||||||
|
soil_data = getWeatherAndSoilData(auth_token,device_id, startTs, endTs, "AVG", 3600000, limit, orderBy,
|
||||||
|
keys_soil)
|
||||||
|
result[device_id]=soil_data
|
||||||
|
return result
|
||||||
|
weather_params={
|
||||||
|
"device_id" : "18d121f0-561b-11f0-a556-4f10f26fc07f",
|
||||||
|
"agg" :"AVG",
|
||||||
|
"interval" : 3600000, #1小时
|
||||||
|
"limit" :"",
|
||||||
|
"orderBy" : "",
|
||||||
|
"keys":"AIR_TEMPERATURE,WIND_SPEED,AIR_LUX,AIR_HUMIDITY,RAIN_FALL_REALTIME"
|
||||||
|
}
|
||||||
|
def get_plot(data_list):
|
||||||
|
df = pd.DataFrame(data_list)
|
||||||
|
df['value'] = pd.to_numeric(df['value'], errors='coerce').round(3)# 转换湿度值为浮点数
|
||||||
|
df['datetime'] = pd.to_datetime(df['datetime']) # 转换为datetime类型
|
||||||
|
|
||||||
|
# 设置画布大小
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
|
||||||
|
# 绘制折线图
|
||||||
|
plt.plot(df['datetime'], df['value'],
|
||||||
|
color='teal',
|
||||||
|
linestyle='-',
|
||||||
|
linewidth=2,
|
||||||
|
marker='o',
|
||||||
|
markersize=4,
|
||||||
|
markerfacecolor='orange',
|
||||||
|
markeredgecolor='orange',
|
||||||
|
label='Soil Moisture (%)')
|
||||||
|
|
||||||
|
# 添加22点后的数据高亮(符合你之前获取夜间数据的需求)
|
||||||
|
night_data = df[df['datetime'].dt.hour >= 22]
|
||||||
|
plt.scatter(night_data['datetime'], night_data['value'],
|
||||||
|
color='red',
|
||||||
|
s=30,
|
||||||
|
label='22:00-06:00 Data')
|
||||||
|
|
||||||
|
# 设置标题和坐标轴标签
|
||||||
|
plt.title('Soil Moisture Time Series (2025-06-22 to 2025-06-30)',
|
||||||
|
fontsize=14,
|
||||||
|
pad=15)
|
||||||
|
plt.xlabel('Datetime', fontsize=12, labelpad=10)
|
||||||
|
plt.ylabel('Soil Moisture (%)', fontsize=12, labelpad=10)
|
||||||
|
|
||||||
|
# 设置x轴刻度旋转,避免重叠
|
||||||
|
plt.xticks(rotation=45, ha='right', fontsize=10)
|
||||||
|
|
||||||
|
# 添加网格线
|
||||||
|
plt.grid(axis='y', linestyle='--', alpha=0.7)
|
||||||
|
|
||||||
|
# 添加图例
|
||||||
|
plt.legend(loc='upper right', fontsize=10)
|
||||||
|
|
||||||
|
# 调整布局
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
|
# 显示图表
|
||||||
|
plt.show()
|
||||||
|
if __name__ == "__main__":
|
||||||
|
auth_token = loginAuth()
|
||||||
|
device_id="18d121f0-561b-11f0-a556-4f10f26fc07f"
|
||||||
|
agg="AVG"
|
||||||
|
interval=21600000
|
||||||
|
limit=""
|
||||||
|
orderBy=""
|
||||||
|
keys="RAIN_FALL_REALTIME"
|
||||||
|
date_start = "2025-06-25 00:00:00.123"
|
||||||
|
date_end = "2025-07-01 23:59:59.123"
|
||||||
|
startTs=dateToTimestamp(date_start)
|
||||||
|
endTs=dateToTimestamp(date_end)
|
||||||
|
# data=get_soil_data_dk1(auth_token,startTs,endTs)
|
||||||
|
# print(data)
|
||||||
|
data=getWeatherAndSoilData(auth_token,device_id,startTs,endTs,"AVG",3600000,limit,orderBy,keys)
|
||||||
|
print(data['RAIN_FALL'])
|
||||||
|
# get_plot(data['1号传感器']['SOIL_MOISTURE_SURFACE'])
|
||||||
|
get_plot(data['RAIN_FALL_REALTIME'])
|
||||||
42
main.py
Normal file
42
main.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI, Query
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from irrgiation.irrigateDecisionDemo import cal_irrigationByDay
|
||||||
|
# 创建 FastAPI 应用
|
||||||
|
app = FastAPI()
|
||||||
|
# 定义请求模型
|
||||||
|
class IrrigationRequest(BaseModel):
|
||||||
|
zwlx_name:str
|
||||||
|
dkbm: str
|
||||||
|
monitor_date: str
|
||||||
|
soil_key: str
|
||||||
|
weather_key: str
|
||||||
|
irrigation:float
|
||||||
|
@app.post("/calculate_irrigation/")
|
||||||
|
async def create_item(data: IrrigationRequest):
|
||||||
|
# 调用气象数据接口--获取具体日期的气象数据(气象)
|
||||||
|
try:
|
||||||
|
start_date = datetime.strptime(data.monitor_date, '%Y-%m-%d')
|
||||||
|
end_date =start_date+timedelta(days=1)
|
||||||
|
result = cal_irrigationByDay(
|
||||||
|
data.zwlx_name,
|
||||||
|
data.dkbm,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
data.soil_key,
|
||||||
|
data.weather_key,
|
||||||
|
data.irrigation
|
||||||
|
)
|
||||||
|
return {"status": "success", "data": result.to_dict(orient="records")}
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
@app.get("/items/lib")
|
||||||
|
async def get_lib():
|
||||||
|
return {"message": "Library data"}
|
||||||
|
return result
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run(app, host="127.0.0.1", port=8001)
|
||||||
99
pyeto/__init__.py
Normal file
99
pyeto/__init__.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
|
||||||
|
import pyeto
|
||||||
|
|
||||||
|
from pyeto.convert import (
|
||||||
|
celsius2kelvin,
|
||||||
|
kelvin2celsius,
|
||||||
|
deg2rad,
|
||||||
|
rad2deg,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pyeto.fao import (
|
||||||
|
atm_pressure,
|
||||||
|
avp_from_tmin,
|
||||||
|
avp_from_rhmin_rhmax,
|
||||||
|
avp_from_rhmax,
|
||||||
|
avp_from_rhmean,
|
||||||
|
avp_from_tdew,
|
||||||
|
avp_from_twet_tdry,
|
||||||
|
cs_rad,
|
||||||
|
daily_mean_t,
|
||||||
|
daylight_hours,
|
||||||
|
delta_svp,
|
||||||
|
energy2evap,
|
||||||
|
et_rad,
|
||||||
|
fao56_penman_monteith,
|
||||||
|
hargreaves,
|
||||||
|
inv_rel_dist_earth_sun,
|
||||||
|
mean_svp,
|
||||||
|
monthly_soil_heat_flux,
|
||||||
|
monthly_soil_heat_flux2,
|
||||||
|
net_in_sol_rad,
|
||||||
|
net_out_lw_rad,
|
||||||
|
net_rad,
|
||||||
|
psy_const,
|
||||||
|
psy_const_of_psychrometer,
|
||||||
|
rh_from_avp_svp,
|
||||||
|
SOLAR_CONSTANT,
|
||||||
|
sol_dec,
|
||||||
|
sol_rad_from_sun_hours,
|
||||||
|
sol_rad_from_t,
|
||||||
|
sol_rad_island,
|
||||||
|
STEFAN_BOLTZMANN_CONSTANT,
|
||||||
|
sunset_hour_angle,
|
||||||
|
svp_from_t,
|
||||||
|
wind_speed_2m,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pyeto.thornthwaite import (
|
||||||
|
thornthwaite,
|
||||||
|
monthly_mean_daylight_hours,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# Unit conversions
|
||||||
|
'celsius2kelvin',
|
||||||
|
'deg2rad',
|
||||||
|
'kelvin2celsius',
|
||||||
|
'rad2deg',
|
||||||
|
|
||||||
|
# FAO equations
|
||||||
|
'atm_pressure',
|
||||||
|
'avp_from_tmin',
|
||||||
|
'avp_from_rhmin_rhmax',
|
||||||
|
'avp_from_rhmax',
|
||||||
|
'avp_from_rhmean',
|
||||||
|
'avp_from_tdew',
|
||||||
|
'avp_from_twet_tdry',
|
||||||
|
'cs_rad',
|
||||||
|
'daily_mean_t',
|
||||||
|
'daylight_hours',
|
||||||
|
'delta_svp',
|
||||||
|
'energy2evap',
|
||||||
|
'et_rad',
|
||||||
|
'fao56_penman_monteith',
|
||||||
|
'hargreaves',
|
||||||
|
'inv_rel_dist_earth_sun',
|
||||||
|
'mean_svp',
|
||||||
|
'monthly_soil_heat_flux',
|
||||||
|
'monthly_soil_heat_flux2',
|
||||||
|
'net_in_sol_rad',
|
||||||
|
'net_out_lw_rad',
|
||||||
|
'net_rad',
|
||||||
|
'psy_const',
|
||||||
|
'psy_const_of_psychrometer',
|
||||||
|
'rh_from_avp_svp',
|
||||||
|
'SOLAR_CONSTANT',
|
||||||
|
'sol_dec',
|
||||||
|
'sol_rad_from_sun_hours',
|
||||||
|
'sol_rad_from_t',
|
||||||
|
'sol_rad_island',
|
||||||
|
'STEFAN_BOLTZMANN_CONSTANT',
|
||||||
|
'sunset_hour_angle',
|
||||||
|
'svp_from_t',
|
||||||
|
'wind_speed_2m',
|
||||||
|
|
||||||
|
# Thornthwaite method
|
||||||
|
'thornthwaite',
|
||||||
|
'monthly_mean_daylight_hours',
|
||||||
|
]
|
||||||
BIN
pyeto/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
pyeto/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pyeto/__pycache__/_check.cpython-310.pyc
Normal file
BIN
pyeto/__pycache__/_check.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pyeto/__pycache__/convert.cpython-310.pyc
Normal file
BIN
pyeto/__pycache__/convert.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pyeto/__pycache__/fao.cpython-310.pyc
Normal file
BIN
pyeto/__pycache__/fao.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pyeto/__pycache__/thornthwaite.cpython-310.pyc
Normal file
BIN
pyeto/__pycache__/thornthwaite.cpython-310.pyc
Normal file
Binary file not shown.
69
pyeto/_check.py
Normal file
69
pyeto/_check.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
"""
|
||||||
|
Internal validation functions.
|
||||||
|
|
||||||
|
:copyright: (c) 2015 by Mark Richards.
|
||||||
|
:license: BSD 3-Clause, see LICENSE.txt for more details.
|
||||||
|
"""
|
||||||
|
from pyeto.convert import deg2rad
|
||||||
|
|
||||||
|
# Internal constants
|
||||||
|
# Latitude
|
||||||
|
_MINLAT_RADIANS = deg2rad(-90.0)
|
||||||
|
_MAXLAT_RADIANS = deg2rad(90.0)
|
||||||
|
|
||||||
|
# Solar declination
|
||||||
|
_MINSOLDEC_RADIANS = deg2rad(-23.5)
|
||||||
|
_MAXSOLDEC_RADIANS = deg2rad(23.5)
|
||||||
|
|
||||||
|
# Sunset hour angle
|
||||||
|
_MINSHA_RADIANS = 0.0
|
||||||
|
_MAXSHA_RADIANS = deg2rad(180)
|
||||||
|
|
||||||
|
|
||||||
|
def check_day_hours(hours, arg_name):
|
||||||
|
"""
|
||||||
|
Check that *hours* is in the range 1 to 24.
|
||||||
|
"""
|
||||||
|
if not 0 <= hours <= 24:
|
||||||
|
raise ValueError(
|
||||||
|
'{0} should be in range 0-24: {1!r}'.format(arg_name, hours))
|
||||||
|
|
||||||
|
|
||||||
|
def check_doy(doy):
|
||||||
|
"""
|
||||||
|
Check day of the year is valid.
|
||||||
|
"""
|
||||||
|
if not 1 <= doy <= 366:
|
||||||
|
raise ValueError(
|
||||||
|
'Day of the year (doy) must be in range 1-366: {0!r}'.format(doy))
|
||||||
|
|
||||||
|
|
||||||
|
def check_latitude_rad(latitude):
|
||||||
|
if not _MINLAT_RADIANS <= latitude <= _MAXLAT_RADIANS:
|
||||||
|
raise ValueError(
|
||||||
|
'latitude outside valid range {0!r} to {1!r} rad: {2!r}'
|
||||||
|
.format(_MINLAT_RADIANS, _MAXLAT_RADIANS, latitude))
|
||||||
|
|
||||||
|
|
||||||
|
def check_sol_dec_rad(sd):
|
||||||
|
"""
|
||||||
|
Solar declination can vary between -23.5 and +23.5 degrees.
|
||||||
|
|
||||||
|
See http://mypages.iit.edu/~maslanka/SolarGeo.pdf
|
||||||
|
"""
|
||||||
|
if not _MINSOLDEC_RADIANS <= sd <= _MAXSOLDEC_RADIANS:
|
||||||
|
raise ValueError(
|
||||||
|
'solar declination outside valid range {0!r} to {1!r} rad: {2!r}'
|
||||||
|
.format(_MINSOLDEC_RADIANS, _MAXSOLDEC_RADIANS, sd))
|
||||||
|
|
||||||
|
|
||||||
|
def check_sunset_hour_angle_rad(sha):
|
||||||
|
"""
|
||||||
|
Sunset hour angle has the range 0 to 180 degrees.
|
||||||
|
|
||||||
|
See http://mypages.iit.edu/~maslanka/SolarGeo.pdf
|
||||||
|
"""
|
||||||
|
if not _MINSHA_RADIANS <= sha <= _MAXSHA_RADIANS:
|
||||||
|
raise ValueError(
|
||||||
|
'sunset hour angle outside valid range {0!r} to {1!r} rad: {2!r}'
|
||||||
|
.format(_MINSHA_RADIANS, _MAXSHA_RADIANS, sha))
|
||||||
52
pyeto/convert.py
Normal file
52
pyeto/convert.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""
|
||||||
|
Unit conversion functions.
|
||||||
|
|
||||||
|
:copyright: (c) 2015 by Mark Richards.
|
||||||
|
:license: BSD 3-Clause, see LICENSE.txt for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
def celsius2kelvin(celsius):
|
||||||
|
"""
|
||||||
|
Convert temperature in degrees Celsius to degrees Kelvin.
|
||||||
|
|
||||||
|
:param celsius: Degrees Celsius
|
||||||
|
:return: Degrees Kelvin
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return celsius + 273.15
|
||||||
|
|
||||||
|
|
||||||
|
def kelvin2celsius(kelvin):
|
||||||
|
"""
|
||||||
|
Convert temperature in degrees Kelvin to degrees Celsius.
|
||||||
|
|
||||||
|
:param kelvin: Degrees Kelvin
|
||||||
|
:return: Degrees Celsius
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return kelvin - 273.15
|
||||||
|
|
||||||
|
|
||||||
|
def deg2rad(degrees):
|
||||||
|
"""
|
||||||
|
Convert angular degrees to radians
|
||||||
|
|
||||||
|
:param degrees: Value in degrees to be converted.
|
||||||
|
:return: Value in radians
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return degrees * (math.pi / 180.0)
|
||||||
|
|
||||||
|
|
||||||
|
def rad2deg(radians):
|
||||||
|
"""
|
||||||
|
Convert radians to angular degrees
|
||||||
|
|
||||||
|
:param radians: Value in radians to be converted.
|
||||||
|
:return: Value in angular degrees
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return radians * (180.0 / math.pi)
|
||||||
737
pyeto/fao.py
Normal file
737
pyeto/fao.py
Normal file
@@ -0,0 +1,737 @@
|
|||||||
|
"""
|
||||||
|
Library of functions for estimating reference evapotransporation (ETo) for
|
||||||
|
a grass reference crop using the FAO-56 Penman-Monteith and Hargreaves
|
||||||
|
equations. The library includes numerous functions for estimating missing
|
||||||
|
meteorological data.
|
||||||
|
|
||||||
|
:copyright: (c) 2015 by Mark Richards.
|
||||||
|
:license: BSD 3-Clause, see LICENSE.txt for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
from ._check import (
|
||||||
|
check_day_hours as _check_day_hours,
|
||||||
|
check_doy as _check_doy,
|
||||||
|
check_latitude_rad as _check_latitude_rad,
|
||||||
|
check_sol_dec_rad as _check_sol_dec_rad,
|
||||||
|
check_sunset_hour_angle_rad as _check_sunset_hour_angle_rad,
|
||||||
|
)
|
||||||
|
|
||||||
|
#: Solar constant [ MJ m-2 min-1]
|
||||||
|
SOLAR_CONSTANT = 0.0820
|
||||||
|
|
||||||
|
# Stefan Boltzmann constant [MJ K-4 m-2 day-1]
|
||||||
|
STEFAN_BOLTZMANN_CONSTANT = 0.000000004903 #
|
||||||
|
"""Stefan Boltzmann constant [MJ K-4 m-2 day-1]"""
|
||||||
|
|
||||||
|
|
||||||
|
def atm_pressure(altitude):
|
||||||
|
"""
|
||||||
|
Estimate atmospheric pressure from altitude.
|
||||||
|
|
||||||
|
Calculated using a simplification of the ideal gas law, assuming 20 degrees
|
||||||
|
Celsius for a standard atmosphere. Based on equation 7, page 62 in Allen
|
||||||
|
et al (1998).
|
||||||
|
|
||||||
|
:param altitude: Elevation/altitude above sea level [m]
|
||||||
|
:return: atmospheric pressure [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
tmp = (293.0 - (0.0065 * altitude)) / 293.0
|
||||||
|
return math.pow(tmp, 5.26) * 101.3
|
||||||
|
|
||||||
|
|
||||||
|
def avp_from_tmin(tmin):
|
||||||
|
"""
|
||||||
|
Estimate actual vapour pressure (*ea*) from minimum temperature.
|
||||||
|
|
||||||
|
This method is to be used where humidity data are lacking or are of
|
||||||
|
questionable quality. The method assumes that the dewpoint temperature
|
||||||
|
is approximately equal to the minimum temperature (*tmin*), i.e. the
|
||||||
|
air is saturated with water vapour at *tmin*.
|
||||||
|
|
||||||
|
**Note**: This assumption may not hold in arid/semi-arid areas.
|
||||||
|
In these areas it may be better to subtract 2 deg C from the
|
||||||
|
minimum temperature (see Annex 6 in FAO paper).
|
||||||
|
|
||||||
|
Based on equation 48 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param tmin: Daily minimum temperature [deg C]
|
||||||
|
:return: Actual vapour pressure [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return 0.611 * math.exp((17.27 * tmin) / (tmin + 237.3))
|
||||||
|
|
||||||
|
|
||||||
|
def avp_from_rhmin_rhmax(svp_tmin, svp_tmax, rh_min, rh_max):
|
||||||
|
"""
|
||||||
|
Estimate actual vapour pressure (*ea*) from saturation vapour pressure and
|
||||||
|
relative humidity.
|
||||||
|
|
||||||
|
Based on FAO equation 17 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param svp_tmin: Saturation vapour pressure at daily minimum temperature
|
||||||
|
[kPa]. Can be estimated using ``svp_from_t()``.
|
||||||
|
:param svp_tmax: Saturation vapour pressure at daily maximum temperature
|
||||||
|
[kPa]. Can be estimated using ``svp_from_t()``.
|
||||||
|
:param rh_min: Minimum relative humidity [%]
|
||||||
|
:param rh_max: Maximum relative humidity [%]
|
||||||
|
:return: Actual vapour pressure [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
tmp1 = svp_tmin * (rh_max / 100.0)
|
||||||
|
tmp2 = svp_tmax * (rh_min / 100.0)
|
||||||
|
return (tmp1 + tmp2) / 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def avp_from_rhmax(svp_tmin, rh_max):
|
||||||
|
"""
|
||||||
|
Estimate actual vapour pressure (*e*a) from saturation vapour pressure at
|
||||||
|
daily minimum temperature and maximum relative humidity
|
||||||
|
|
||||||
|
Based on FAO equation 18 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param svp_tmin: Saturation vapour pressure at daily minimum temperature
|
||||||
|
[kPa]. Can be estimated using ``svp_from_t()``.
|
||||||
|
:param rh_max: Maximum relative humidity [%]
|
||||||
|
:return: Actual vapour pressure [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return svp_tmin * (rh_max / 100.0)
|
||||||
|
|
||||||
|
|
||||||
|
def avp_from_rhmean(svp_tmin, svp_tmax, rh_mean):
|
||||||
|
"""
|
||||||
|
Estimate actual vapour pressure (*ea*) from saturation vapour pressure at
|
||||||
|
daily minimum and maximum temperature, and mean relative humidity.
|
||||||
|
|
||||||
|
Based on FAO equation 19 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param svp_tmin: Saturation vapour pressure at daily minimum temperature
|
||||||
|
[kPa]. Can be estimated using ``svp_from_t()``.
|
||||||
|
:param svp_tmax: Saturation vapour pressure at daily maximum temperature
|
||||||
|
[kPa]. Can be estimated using ``svp_from_t()``.
|
||||||
|
:param rh_mean: Mean relative humidity [%] (average of RH min and RH max).
|
||||||
|
:return: Actual vapour pressure [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return (rh_mean / 100.0) * ((svp_tmax + svp_tmin) / 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
def avp_from_tdew(tdew):
|
||||||
|
"""
|
||||||
|
Estimate actual vapour pressure (*ea*) from dewpoint temperature.
|
||||||
|
|
||||||
|
Based on equation 14 in Allen et al (1998). As the dewpoint temperature is
|
||||||
|
the temperature to which air needs to be cooled to make it saturated, the
|
||||||
|
actual vapour pressure is the saturation vapour pressure at the dewpoint
|
||||||
|
temperature.
|
||||||
|
|
||||||
|
This method is preferable to calculating vapour pressure from
|
||||||
|
minimum temperature.
|
||||||
|
|
||||||
|
:param tdew: Dewpoint temperature [deg C]
|
||||||
|
:return: Actual vapour pressure [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return 0.6108 * math.exp((17.27 * tdew) / (tdew + 237.3))
|
||||||
|
|
||||||
|
|
||||||
|
def avp_from_twet_tdry(twet, tdry, svp_twet, psy_const):
|
||||||
|
"""
|
||||||
|
Estimate actual vapour pressure (*ea*) from wet and dry bulb temperature.
|
||||||
|
|
||||||
|
Based on equation 15 in Allen et al (1998). As the dewpoint temperature
|
||||||
|
is the temperature to which air needs to be cooled to make it saturated, the
|
||||||
|
actual vapour pressure is the saturation vapour pressure at the dewpoint
|
||||||
|
temperature.
|
||||||
|
|
||||||
|
This method is preferable to calculating vapour pressure from
|
||||||
|
minimum temperature.
|
||||||
|
|
||||||
|
Values for the psychrometric constant of the psychrometer (*psy_const*)
|
||||||
|
can be calculated using ``psyc_const_of_psychrometer()``.
|
||||||
|
|
||||||
|
:param twet: Wet bulb temperature [deg C]
|
||||||
|
:param tdry: Dry bulb temperature [deg C]
|
||||||
|
:param svp_twet: Saturated vapour pressure at the wet bulb temperature
|
||||||
|
[kPa]. Can be estimated using ``svp_from_t()``.
|
||||||
|
:param psy_const: Psychrometric constant of the pyschrometer [kPa deg C-1].
|
||||||
|
Can be estimated using ``psy_const()`` or
|
||||||
|
``psy_const_of_psychrometer()``.
|
||||||
|
:return: Actual vapour pressure [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return svp_twet - (psy_const * (tdry - twet))
|
||||||
|
|
||||||
|
|
||||||
|
def cs_rad(altitude, et_rad):
|
||||||
|
"""
|
||||||
|
Estimate clear sky radiation from altitude and extraterrestrial radiation.
|
||||||
|
|
||||||
|
Based on equation 37 in Allen et al (1998) which is recommended when
|
||||||
|
calibrated Angstrom values are not available.
|
||||||
|
|
||||||
|
:param altitude: Elevation above sea level [m]
|
||||||
|
:param et_rad: Extraterrestrial radiation [MJ m-2 day-1]. Can be
|
||||||
|
estimated using ``et_rad()``.
|
||||||
|
:return: Clear sky radiation [MJ m-2 day-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return (0.00002 * altitude + 0.75) * et_rad
|
||||||
|
|
||||||
|
|
||||||
|
def daily_mean_t(tmin, tmax):
|
||||||
|
"""
|
||||||
|
Estimate mean daily temperature from the daily minimum and maximum
|
||||||
|
temperatures.
|
||||||
|
|
||||||
|
:param tmin: Minimum daily temperature [deg C]
|
||||||
|
:param tmax: Maximum daily temperature [deg C]
|
||||||
|
:return: Mean daily temperature [deg C]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return (tmax + tmin) / 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def daylight_hours(sha):
|
||||||
|
"""
|
||||||
|
Calculate daylight hours from sunset hour angle.
|
||||||
|
|
||||||
|
Based on FAO equation 34 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param sha: Sunset hour angle [rad]. Can be calculated using
|
||||||
|
``sunset_hour_angle()``.
|
||||||
|
:return: Daylight hours.
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
_check_sunset_hour_angle_rad(sha)
|
||||||
|
return (24.0 / math.pi) * sha
|
||||||
|
|
||||||
|
|
||||||
|
def delta_svp(t):
|
||||||
|
"""
|
||||||
|
Estimate the slope of the saturation vapour pressure curve at a given
|
||||||
|
temperature.
|
||||||
|
|
||||||
|
Based on equation 13 in Allen et al (1998). If using in the Penman-Monteith
|
||||||
|
*t* should be the mean air temperature.
|
||||||
|
|
||||||
|
:param t: Air temperature [deg C]. Use mean air temperature for use in
|
||||||
|
Penman-Monteith.
|
||||||
|
:return: Saturation vapour pressure [kPa degC-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
tmp = 4098 * (0.6108 * math.exp((17.27 * t) / (t + 237.3)))
|
||||||
|
return tmp / math.pow((t + 237.3), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def energy2evap(energy):
|
||||||
|
"""
|
||||||
|
Convert energy (e.g. radiation energy) in MJ m-2 day-1 to the equivalent
|
||||||
|
evaporation, assuming a grass reference crop.
|
||||||
|
|
||||||
|
Energy is converted to equivalent evaporation using a conversion
|
||||||
|
factor equal to the inverse of the latent heat of vapourisation
|
||||||
|
(1 / lambda = 0.408).
|
||||||
|
|
||||||
|
Based on FAO equation 20 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param energy: Energy e.g. radiation or heat flux [MJ m-2 day-1].
|
||||||
|
:return: Equivalent evaporation [mm day-1].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return 0.408 * energy
|
||||||
|
|
||||||
|
|
||||||
|
def et_rad(latitude, sol_dec, sha, ird):
|
||||||
|
"""
|
||||||
|
Estimate daily extraterrestrial radiation (*Ra*, 'top of the atmosphere
|
||||||
|
radiation').
|
||||||
|
|
||||||
|
Based on equation 21 in Allen et al (1998). If monthly mean radiation is
|
||||||
|
required make sure *sol_dec*. *sha* and *irl* have been calculated using
|
||||||
|
the day of the year that corresponds to the middle of the month.
|
||||||
|
|
||||||
|
**Note**: From Allen et al (1998): "For the winter months in latitudes
|
||||||
|
greater than 55 degrees (N or S), the equations have limited validity.
|
||||||
|
Reference should be made to the Smithsonian Tables to assess possible
|
||||||
|
deviations."
|
||||||
|
|
||||||
|
:param latitude: Latitude [radians]
|
||||||
|
:param sol_dec: Solar declination [radians]. Can be calculated using
|
||||||
|
``sol_dec()``.
|
||||||
|
:param sha: Sunset hour angle [radians]. Can be calculated using
|
||||||
|
``sunset_hour_angle()``.
|
||||||
|
:param ird: Inverse relative distance earth-sun [dimensionless]. Can be
|
||||||
|
calculated using ``inv_rel_dist_earth_sun()``.
|
||||||
|
:return: Daily extraterrestrial radiation [MJ m-2 day-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
_check_latitude_rad(latitude)
|
||||||
|
_check_sol_dec_rad(sol_dec)
|
||||||
|
_check_sunset_hour_angle_rad(sha)
|
||||||
|
|
||||||
|
tmp1 = (24.0 * 60.0) / math.pi
|
||||||
|
tmp2 = sha * math.sin(latitude) * math.sin(sol_dec)
|
||||||
|
tmp3 = math.cos(latitude) * math.cos(sol_dec) * math.sin(sha)
|
||||||
|
return tmp1 * SOLAR_CONSTANT * ird * (tmp2 + tmp3)
|
||||||
|
|
||||||
|
|
||||||
|
def fao56_penman_monteith(net_rad, t, ws, svp, avp, delta_svp, psy, shf=0.0):
|
||||||
|
"""
|
||||||
|
Estimate reference evapotranspiration (ETo) from a hypothetical
|
||||||
|
short grass reference surface using the FAO-56 Penman-Monteith equation.
|
||||||
|
|
||||||
|
Based on equation 6 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param net_rad: Net radiation at crop surface [MJ m-2 day-1]. If
|
||||||
|
necessary this can be estimated using ``net_rad()``.
|
||||||
|
:param t: Air temperature at 2 m height [deg Kelvin].
|
||||||
|
:param ws: Wind speed at 2 m height [m s-1]. If not measured at 2m,
|
||||||
|
convert using ``wind_speed_at_2m()``.
|
||||||
|
:param svp: Saturation vapour pressure [kPa]. Can be estimated using
|
||||||
|
``svp_from_t()''.
|
||||||
|
:param avp: Actual vapour pressure [kPa]. Can be estimated using a range
|
||||||
|
of functions with names beginning with 'avp_from'.
|
||||||
|
:param delta_svp: Slope of saturation vapour pressure curve [kPa degC-1].
|
||||||
|
Can be estimated using ``delta_svp()``.
|
||||||
|
:param psy: Psychrometric constant [kPa deg C]. Can be estimatred using
|
||||||
|
``psy_const_of_psychrometer()`` or ``psy_const()``.
|
||||||
|
:param shf: Soil heat flux (G) [MJ m-2 day-1] (default is 0.0, which is
|
||||||
|
reasonable for a daily or 10-day time steps). For monthly time steps
|
||||||
|
*shf* can be estimated using ``monthly_soil_heat_flux()`` or
|
||||||
|
``monthly_soil_heat_flux2()``.
|
||||||
|
:return: Reference evapotranspiration (ETo) from a hypothetical
|
||||||
|
grass reference surface [mm day-1].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
a1 = (0.408 * (net_rad - shf) * delta_svp /
|
||||||
|
(delta_svp + (psy * (1 + 0.34 * ws))))
|
||||||
|
a2 = (900 * ws / (t+273) * (svp - avp) * psy /
|
||||||
|
(delta_svp + (psy * (1 + 0.34 * ws))))
|
||||||
|
return a1 + a2
|
||||||
|
|
||||||
|
|
||||||
|
def hargreaves(tmin, tmax, tmean, et_rad):
|
||||||
|
"""
|
||||||
|
Estimate reference evapotranspiration over grass (ETo) using the Hargreaves
|
||||||
|
equation.
|
||||||
|
|
||||||
|
Generally, when solar radiation data, relative humidity data
|
||||||
|
and/or wind speed data are missing, it is better to estimate them using
|
||||||
|
the functions available in this module, and then calculate ETo
|
||||||
|
the FAO Penman-Monteith equation. However, as an alternative, ETo can be
|
||||||
|
estimated using the Hargreaves ETo equation.
|
||||||
|
|
||||||
|
Based on equation 52 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param tmin: Minimum daily temperature [deg C]
|
||||||
|
:param tmax: Maximum daily temperature [deg C]
|
||||||
|
:param tmean: Mean daily temperature [deg C]. If emasurements not
|
||||||
|
available it can be estimated as (*tmin* + *tmax*) / 2.
|
||||||
|
:param et_rad: Extraterrestrial radiation (Ra) [MJ m-2 day-1]. Can be
|
||||||
|
estimated using ``et_rad()``.
|
||||||
|
:return: Reference evapotranspiration over grass (ETo) [mm day-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
# Note, multiplied by 0.408 to convert extraterrestrial radiation could
|
||||||
|
# be given in MJ m-2 day-1 rather than as equivalent evaporation in
|
||||||
|
# mm day-1
|
||||||
|
return 0.0023 * (tmean + 17.8) * (tmax - tmin) ** 0.5 * 0.408 * et_rad
|
||||||
|
|
||||||
|
|
||||||
|
def inv_rel_dist_earth_sun(day_of_year):
|
||||||
|
"""
|
||||||
|
Calculate the inverse relative distance between earth and sun from
|
||||||
|
day of the year.
|
||||||
|
|
||||||
|
Based on FAO equation 23 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param day_of_year: Day of the year [1 to 366]
|
||||||
|
:return: Inverse relative distance between earth and the sun
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
_check_doy(day_of_year)
|
||||||
|
return 1 + (0.033 * math.cos((2.0 * math.pi / 365.0) * day_of_year))
|
||||||
|
|
||||||
|
|
||||||
|
def mean_svp(tmin, tmax):
|
||||||
|
"""
|
||||||
|
Estimate mean saturation vapour pressure, *es* [kPa] from minimum and
|
||||||
|
maximum temperature.
|
||||||
|
|
||||||
|
Based on equations 11 and 12 in Allen et al (1998).
|
||||||
|
|
||||||
|
Mean saturation vapour pressure is calculated as the mean of the
|
||||||
|
saturation vapour pressure at tmax (maximum temperature) and tmin
|
||||||
|
(minimum temperature).
|
||||||
|
|
||||||
|
:param tmin: Minimum temperature [deg C]
|
||||||
|
:param tmax: Maximum temperature [deg C]
|
||||||
|
:return: Mean saturation vapour pressure (*es*) [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return (svp_from_t(tmin) + svp_from_t(tmax)) / 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def monthly_soil_heat_flux(t_month_prev, t_month_next):
|
||||||
|
"""
|
||||||
|
Estimate monthly soil heat flux (Gmonth) from the mean air temperature of
|
||||||
|
the previous and next month, assuming a grass crop.
|
||||||
|
|
||||||
|
Based on equation 43 in Allen et al (1998). If the air temperature of the
|
||||||
|
next month is not known use ``monthly_soil_heat_flux2()`` instead. The
|
||||||
|
resulting heat flux can be converted to equivalent evaporation [mm day-1]
|
||||||
|
using ``energy2evap()``.
|
||||||
|
|
||||||
|
:param t_month_prev: Mean air temperature of the previous month
|
||||||
|
[deg Celsius]
|
||||||
|
:param t_month2_next: Mean air temperature of the next month [deg Celsius]
|
||||||
|
:return: Monthly soil heat flux (Gmonth) [MJ m-2 day-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return 0.07 * (t_month_next - t_month_prev)
|
||||||
|
|
||||||
|
|
||||||
|
def monthly_soil_heat_flux2(t_month_prev, t_month_cur):
|
||||||
|
"""
|
||||||
|
Estimate monthly soil heat flux (Gmonth) [MJ m-2 day-1] from the mean
|
||||||
|
air temperature of the previous and current month, assuming a grass crop.
|
||||||
|
|
||||||
|
Based on equation 44 in Allen et al (1998). If the air temperature of the
|
||||||
|
next month is available, use ``monthly_soil_heat_flux()`` instead. The
|
||||||
|
resulting heat flux can be converted to equivalent evaporation [mm day-1]
|
||||||
|
using ``energy2evap()``.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
:param t_month_prev: Mean air temperature of the previous month
|
||||||
|
[deg Celsius]
|
||||||
|
:param t_month_cur: Mean air temperature of the current month [deg Celsius]
|
||||||
|
:return: Monthly soil heat flux (Gmonth) [MJ m-2 day-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return 0.14 * (t_month_cur - t_month_prev)
|
||||||
|
|
||||||
|
|
||||||
|
def net_in_sol_rad(sol_rad, albedo=0.23):
|
||||||
|
"""
|
||||||
|
Calculate net incoming solar (or shortwave) radiation from gross
|
||||||
|
incoming solar radiation, assuming a grass reference crop.
|
||||||
|
|
||||||
|
Net incoming solar radiation is the net shortwave radiation resulting
|
||||||
|
from the balance between incoming and reflected solar radiation. The
|
||||||
|
output can be converted to equivalent evaporation [mm day-1] using
|
||||||
|
``energy2evap()``.
|
||||||
|
|
||||||
|
Based on FAO equation 38 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param sol_rad: Gross incoming solar radiation [MJ m-2 day-1]. If
|
||||||
|
necessary this can be estimated using functions whose name
|
||||||
|
begins with 'sol_rad_from'.
|
||||||
|
:param albedo: Albedo of the crop as the proportion of gross incoming solar
|
||||||
|
radiation that is reflected by the surface. Default value is 0.23,
|
||||||
|
which is the value used by the FAO for a short grass reference crop.
|
||||||
|
Albedo can be as high as 0.95 for freshly fallen snow and as low as
|
||||||
|
0.05 for wet bare soil. A green vegetation over has an albedo of
|
||||||
|
about 0.20-0.25 (Allen et al, 1998).
|
||||||
|
:return: Net incoming solar (or shortwave) radiation [MJ m-2 day-1].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return (1 - albedo) * sol_rad
|
||||||
|
|
||||||
|
|
||||||
|
def net_out_lw_rad(tmin, tmax, sol_rad, cs_rad, avp):
|
||||||
|
"""
|
||||||
|
Estimate net outgoing longwave radiation.
|
||||||
|
|
||||||
|
This is the net longwave energy (net energy flux) leaving the
|
||||||
|
earth's surface. It is proportional to the absolute temperature of
|
||||||
|
the surface raised to the fourth power according to the Stefan-Boltzmann
|
||||||
|
law. However, water vapour, clouds, carbon dioxide and dust are absorbers
|
||||||
|
and emitters of longwave radiation. This function corrects the Stefan-
|
||||||
|
Boltzmann law for humidity (using actual vapor pressure) and cloudiness
|
||||||
|
(using solar radiation and clear sky radiation). The concentrations of all
|
||||||
|
other absorbers are assumed to be constant.
|
||||||
|
|
||||||
|
The output can be converted to equivalent evaporation [mm day-1] using
|
||||||
|
``energy2evap()``.
|
||||||
|
|
||||||
|
Based on FAO equation 39 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param tmin: Absolute daily minimum temperature [degrees Kelvin]
|
||||||
|
:param tmax: Absolute daily maximum temperature [degrees Kelvin]
|
||||||
|
:param sol_rad: Solar radiation [MJ m-2 day-1]. If necessary this can be
|
||||||
|
estimated using ``sol+rad()``.
|
||||||
|
:param cs_rad: Clear sky radiation [MJ m-2 day-1]. Can be estimated using
|
||||||
|
``cs_rad()``.
|
||||||
|
:param avp: Actual vapour pressure [kPa]. Can be estimated using functions
|
||||||
|
with names beginning with 'avp_from'.
|
||||||
|
:return: Net outgoing longwave radiation [MJ m-2 day-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
tmax_k = tmax + 273.15
|
||||||
|
tmin_k = tmin + 273.15
|
||||||
|
tmp1 = (STEFAN_BOLTZMANN_CONSTANT *
|
||||||
|
((math.pow(tmax_k, 4) + math.pow(tmin_k, 4)) / 2))
|
||||||
|
tmp2 = (0.34 - (0.14 * math.sqrt(avp)))
|
||||||
|
tmp3 = 1.35 * (sol_rad / cs_rad) - 0.35
|
||||||
|
return tmp1 * tmp2 * tmp3
|
||||||
|
|
||||||
|
|
||||||
|
def net_rad(ni_sw_rad, no_lw_rad):
|
||||||
|
"""
|
||||||
|
Calculate daily net radiation at the crop surface, assuming a grass
|
||||||
|
reference crop.
|
||||||
|
|
||||||
|
Net radiation is the difference between the incoming net shortwave (or
|
||||||
|
solar) radiation and the outgoing net longwave radiation. Output can be
|
||||||
|
converted to equivalent evaporation [mm day-1] using ``energy2evap()``.
|
||||||
|
|
||||||
|
Based on equation 40 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param ni_sw_rad: Net incoming shortwave radiation [MJ m-2 day-1]. Can be
|
||||||
|
estimated using ``net_in_sol_rad()``.
|
||||||
|
:param no_lw_rad: Net outgoing longwave radiation [MJ m-2 day-1]. Can be
|
||||||
|
estimated using ``net_out_lw_rad()``.
|
||||||
|
:return: Daily net radiation [MJ m-2 day-1].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return ni_sw_rad - no_lw_rad
|
||||||
|
|
||||||
|
|
||||||
|
def psy_const(atmos_pres):
|
||||||
|
"""
|
||||||
|
Calculate the psychrometric constant.
|
||||||
|
|
||||||
|
This method assumes that the air is saturated with water vapour at the
|
||||||
|
minimum daily temperature. This assumption may not hold in arid areas.
|
||||||
|
|
||||||
|
Based on equation 8, page 95 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param atmos_pres: Atmospheric pressure [kPa]. Can be estimated using
|
||||||
|
``atm_pressure()``.
|
||||||
|
:return: Psychrometric constant [kPa degC-1].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return 0.000665 * atmos_pres
|
||||||
|
|
||||||
|
|
||||||
|
def psy_const_of_psychrometer(psychrometer, atmos_pres):
|
||||||
|
"""
|
||||||
|
Calculate the psychrometric constant for different types of
|
||||||
|
psychrometer at a given atmospheric pressure.
|
||||||
|
|
||||||
|
Based on FAO equation 16 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param psychrometer: Integer between 1 and 3 which denotes type of
|
||||||
|
psychrometer:
|
||||||
|
1. ventilated (Asmann or aspirated type) psychrometer with
|
||||||
|
an air movement of approximately 5 m/s
|
||||||
|
2. natural ventilated psychrometer with an air movement
|
||||||
|
of approximately 1 m/s
|
||||||
|
3. non ventilated psychrometer installed indoors
|
||||||
|
:param atmos_pres: Atmospheric pressure [kPa]. Can be estimated using
|
||||||
|
``atm_pressure()``.
|
||||||
|
:return: Psychrometric constant [kPa degC-1].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
# Select coefficient based on type of ventilation of the wet bulb
|
||||||
|
if psychrometer == 1:
|
||||||
|
psy_coeff = 0.000662
|
||||||
|
elif psychrometer == 2:
|
||||||
|
psy_coeff = 0.000800
|
||||||
|
elif psychrometer == 3:
|
||||||
|
psy_coeff = 0.001200
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
'psychrometer should be in range 1 to 3: {0!r}'.format(psychrometer))
|
||||||
|
|
||||||
|
return psy_coeff * atmos_pres
|
||||||
|
|
||||||
|
|
||||||
|
def rh_from_avp_svp(avp, svp):
|
||||||
|
"""
|
||||||
|
Calculate relative humidity as the ratio of actual vapour pressure
|
||||||
|
to saturation vapour pressure at the same temperature.
|
||||||
|
|
||||||
|
See Allen et al (1998), page 67 for details.
|
||||||
|
|
||||||
|
:param avp: Actual vapour pressure [units do not matter so long as they
|
||||||
|
are the same as for *svp*]. Can be estimated using functions whose
|
||||||
|
name begins with 'avp_from'.
|
||||||
|
:param svp: Saturated vapour pressure [units do not matter so long as they
|
||||||
|
are the same as for *avp*]. Can be estimated using ``svp_from_t()``.
|
||||||
|
:return: Relative humidity [%].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return 100.0 * avp / svp
|
||||||
|
|
||||||
|
|
||||||
|
def sol_dec(day_of_year):
|
||||||
|
"""
|
||||||
|
Calculate solar declination from day of the year.
|
||||||
|
|
||||||
|
Based on FAO equation 24 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param day_of_year: Day of year integer between 1 and 365 or 366).
|
||||||
|
:return: solar declination [radians]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
_check_doy(day_of_year)
|
||||||
|
return 0.409 * math.sin(((2.0 * math.pi / 365.0) * day_of_year - 1.39))
|
||||||
|
|
||||||
|
|
||||||
|
def sol_rad_from_sun_hours(daylight_hours, sunshine_hours, et_rad):
|
||||||
|
"""
|
||||||
|
Calculate incoming solar (or shortwave) radiation, *Rs* (radiation hitting
|
||||||
|
a horizontal plane after scattering by the atmosphere) from relative
|
||||||
|
sunshine duration.
|
||||||
|
|
||||||
|
If measured radiation data are not available this method is preferable
|
||||||
|
to calculating solar radiation from temperature. If a monthly mean is
|
||||||
|
required then divide the monthly number of sunshine hours by number of
|
||||||
|
days in the month and ensure that *et_rad* and *daylight_hours* was
|
||||||
|
calculated using the day of the year that corresponds to the middle of
|
||||||
|
the month.
|
||||||
|
|
||||||
|
Based on equations 34 and 35 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param dl_hours: Number of daylight hours [hours]. Can be calculated
|
||||||
|
using ``daylight_hours()``.
|
||||||
|
:param sunshine_hours: Sunshine duration [hours].
|
||||||
|
:param et_rad: Extraterrestrial radiation [MJ m-2 day-1]. Can be
|
||||||
|
estimated using ``et_rad()``.
|
||||||
|
:return: Incoming solar (or shortwave) radiation [MJ m-2 day-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
_check_day_hours(sunshine_hours, 'sun_hours')
|
||||||
|
_check_day_hours(daylight_hours, 'daylight_hours')
|
||||||
|
|
||||||
|
# 0.5 and 0.25 are default values of regression constants (Angstrom values)
|
||||||
|
# recommended by FAO when calibrated values are unavailable.
|
||||||
|
return (0.5 * sunshine_hours / daylight_hours + 0.25) * et_rad
|
||||||
|
|
||||||
|
|
||||||
|
def sol_rad_from_t(et_rad, cs_rad, tmin, tmax, coastal):
|
||||||
|
"""
|
||||||
|
Estimate incoming solar (or shortwave) radiation, *Rs*, (radiation hitting
|
||||||
|
a horizontal plane after scattering by the atmosphere) from min and max
|
||||||
|
temperature together with an empirical adjustment coefficient for
|
||||||
|
'interior' and 'coastal' regions.
|
||||||
|
|
||||||
|
The formula is based on equation 50 in Allen et al (1998) which is the
|
||||||
|
Hargreaves radiation formula (Hargreaves and Samani, 1982, 1985). This
|
||||||
|
method should be used only when solar radiation or sunshine hours data are
|
||||||
|
not available. It is only recommended for locations where it is not
|
||||||
|
possible to use radiation data from a regional station (either because
|
||||||
|
climate conditions are heterogeneous or data are lacking).
|
||||||
|
|
||||||
|
**NOTE**: this method is not suitable for island locations due to the
|
||||||
|
moderating effects of the surrounding water.
|
||||||
|
|
||||||
|
:param et_rad: Extraterrestrial radiation [MJ m-2 day-1]. Can be
|
||||||
|
estimated using ``et_rad()``.
|
||||||
|
:param cs_rad: Clear sky radiation [MJ m-2 day-1]. Can be estimated
|
||||||
|
using ``cs_rad()``.
|
||||||
|
:param tmin: Daily minimum temperature [deg C].
|
||||||
|
:param tmax: Daily maximum temperature [deg C].
|
||||||
|
:param coastal: ``True`` if site is a coastal location, situated on or
|
||||||
|
adjacent to coast of a large land mass and where air masses are
|
||||||
|
influenced by a nearby water body, ``False`` if interior location
|
||||||
|
where land mass dominates and air masses are not strongly influenced
|
||||||
|
by a large water body.
|
||||||
|
:return: Incoming solar (or shortwave) radiation (Rs) [MJ m-2 day-1].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
# Determine value of adjustment coefficient [deg C-0.5] for
|
||||||
|
# coastal/interior locations
|
||||||
|
if coastal:
|
||||||
|
adj = 0.19
|
||||||
|
else:
|
||||||
|
adj = 0.16
|
||||||
|
|
||||||
|
sol_rad = adj * math.sqrt(tmax - tmin) * et_rad
|
||||||
|
|
||||||
|
# The solar radiation value is constrained by the clear sky radiation
|
||||||
|
return min(sol_rad, cs_rad)
|
||||||
|
|
||||||
|
|
||||||
|
def sol_rad_island(et_rad):
|
||||||
|
"""
|
||||||
|
Estimate incoming solar (or shortwave) radiation, *Rs* (radiation hitting
|
||||||
|
a horizontal plane after scattering by the atmosphere) for an island
|
||||||
|
location.
|
||||||
|
|
||||||
|
An island is defined as a land mass with width perpendicular to the
|
||||||
|
coastline <= 20 km. Use this method only if radiation data from
|
||||||
|
elsewhere on the island is not available.
|
||||||
|
|
||||||
|
**NOTE**: This method is only applicable for low altitudes (0-100 m)
|
||||||
|
and monthly calculations.
|
||||||
|
|
||||||
|
Based on FAO equation 51 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param et_rad: Extraterrestrial radiation [MJ m-2 day-1]. Can be
|
||||||
|
estimated using ``et_rad()``.
|
||||||
|
:return: Incoming solar (or shortwave) radiation [MJ m-2 day-1].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return (0.7 * et_rad) - 4.0
|
||||||
|
|
||||||
|
|
||||||
|
def sunset_hour_angle(latitude, sol_dec):
|
||||||
|
"""
|
||||||
|
Calculate sunset hour angle (*Ws*) from latitude and solar
|
||||||
|
declination.
|
||||||
|
|
||||||
|
Based on FAO equation 25 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param latitude: Latitude [radians]. Note: *latitude* should be negative
|
||||||
|
if it in the southern hemisphere, positive if in the northern
|
||||||
|
hemisphere.
|
||||||
|
:param sol_dec: Solar declination [radians]. Can be calculated using
|
||||||
|
``sol_dec()``.
|
||||||
|
:return: Sunset hour angle [radians].
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
_check_latitude_rad(latitude)
|
||||||
|
_check_sol_dec_rad(sol_dec)
|
||||||
|
|
||||||
|
cos_sha = -math.tan(latitude) * math.tan(sol_dec)
|
||||||
|
# If tmp is >= 1 there is no sunset, i.e. 24 hours of daylight
|
||||||
|
# If tmp is <= 1 there is no sunrise, i.e. 24 hours of darkness
|
||||||
|
# See http://www.itacanet.org/the-sun-as-a-source-of-energy/
|
||||||
|
# part-3-calculating-solar-angles/
|
||||||
|
# Domain of acos is -1 <= x <= 1 radians (this is not mentioned in FAO-56!)
|
||||||
|
return math.acos(min(max(cos_sha, -1.0), 1.0))
|
||||||
|
|
||||||
|
|
||||||
|
def svp_from_t(t):
|
||||||
|
"""
|
||||||
|
Estimate saturation vapour pressure (*es*) from air temperature.
|
||||||
|
|
||||||
|
Based on equations 11 and 12 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param t: Temperature [deg C]
|
||||||
|
:return: Saturation vapour pressure [kPa]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return 0.6108 * math.exp((17.27 * t) / (t + 237.3))
|
||||||
|
|
||||||
|
|
||||||
|
def wind_speed_2m(ws, z):
|
||||||
|
"""
|
||||||
|
Convert wind speed measured at different heights above the soil
|
||||||
|
surface to wind speed at 2 m above the surface, assuming a short grass
|
||||||
|
surface.
|
||||||
|
|
||||||
|
Based on FAO equation 47 in Allen et al (1998).
|
||||||
|
|
||||||
|
:param ws: Measured wind speed [m s-1]
|
||||||
|
:param z: Height of wind measurement above ground surface [m]
|
||||||
|
:return: Wind speed at 2 m above the surface [m s-1]
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
return ws * (4.87 / math.log((67.8 * z) - 5.42))
|
||||||
119
pyeto/thornthwaite.py
Normal file
119
pyeto/thornthwaite.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
"""
|
||||||
|
Calculate potential evapotranspiration using the Thornthwaite (1948 method)
|
||||||
|
|
||||||
|
:copyright: (c) 2015 by Mark Richards.
|
||||||
|
:license: BSD 3-Clause, see LICENSE.txt for more details.
|
||||||
|
|
||||||
|
References
|
||||||
|
----------
|
||||||
|
Thornthwaite CW (1948) An approach toward a rational classification of
|
||||||
|
climate. Geographical Review, 38, 55-94.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import calendar
|
||||||
|
|
||||||
|
from . import fao
|
||||||
|
from ._check import check_latitude_rad as _check_latitude_rad
|
||||||
|
|
||||||
|
_MONTHDAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||||
|
_LEAP_MONTHDAYS = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||||
|
|
||||||
|
|
||||||
|
def thornthwaite(monthly_t, monthly_mean_dlh, year=None):
|
||||||
|
"""
|
||||||
|
Estimate monthly potential evapotranspiration (PET) using the
|
||||||
|
Thornthwaite (1948) method.
|
||||||
|
|
||||||
|
Thornthwaite equation:
|
||||||
|
|
||||||
|
*PET* = 1.6 (*L*/12) (*N*/30) (10*Ta* / *I*)***a*
|
||||||
|
|
||||||
|
where:
|
||||||
|
|
||||||
|
* *Ta* is the mean daily air temperature [deg C, if negative use 0] of the
|
||||||
|
month being calculated
|
||||||
|
* *N* is the number of days in the month being calculated
|
||||||
|
* *L* is the mean day length [hours] of the month being calculated
|
||||||
|
* *a* = (6.75 x 10-7)*I***3 - (7.71 x 10-5)*I***2 + (1.792 x 10-2)*I* + 0.49239
|
||||||
|
* *I* is a heat index which depends on the 12 monthly mean temperatures and
|
||||||
|
is calculated as the sum of (*Tai* / 5)**1.514 for each month, where
|
||||||
|
Tai is the air temperature for each month in the year
|
||||||
|
|
||||||
|
:param monthly_t: Iterable containing mean daily air temperature for each
|
||||||
|
month of the year [deg C].
|
||||||
|
:param monthly_mean_dlh: Iterable containing mean daily daylight
|
||||||
|
hours for each month of the year (hours]. These can be calculated
|
||||||
|
using ``monthly_mean_daylight_hours()``.
|
||||||
|
:param year: Year for which PET is required. The only effect of year is
|
||||||
|
to change the number of days in February to 29 if it is a leap year.
|
||||||
|
If it is left as the default (None), then the year is assumed not to
|
||||||
|
be a leap year.
|
||||||
|
:return: Estimated monthly potential evaporation of each month of the year
|
||||||
|
[mm/month]
|
||||||
|
:rtype: List of floats
|
||||||
|
"""
|
||||||
|
if len(monthly_t) != 12:
|
||||||
|
raise ValueError(
|
||||||
|
'monthly_t should be length 12 but is length {0}.'
|
||||||
|
.format(len(monthly_t)))
|
||||||
|
if len(monthly_mean_dlh) != 12:
|
||||||
|
raise ValueError(
|
||||||
|
'monthly_mean_dlh should be length 12 but is length {0}.'
|
||||||
|
.format(len(monthly_mean_dlh)))
|
||||||
|
|
||||||
|
if year is None or not calendar.isleap(year):
|
||||||
|
month_days = _MONTHDAYS
|
||||||
|
else:
|
||||||
|
month_days = _LEAP_MONTHDAYS
|
||||||
|
|
||||||
|
# Negative temperatures should be set to zero
|
||||||
|
adj_monthly_t = [t * (t >= 0) for t in monthly_t]
|
||||||
|
|
||||||
|
# Calculate the heat index (I)
|
||||||
|
I = 0.0
|
||||||
|
for Tai in adj_monthly_t:
|
||||||
|
if Tai / 5.0 > 0.0:
|
||||||
|
I += (Tai / 5.0) ** 1.514
|
||||||
|
|
||||||
|
a = (6.75e-07 * I ** 3) - (7.71e-05 * I ** 2) + (1.792e-02 * I) + 0.49239
|
||||||
|
|
||||||
|
pet = []
|
||||||
|
for Ta, L, N in zip(adj_monthly_t, monthly_mean_dlh, month_days):
|
||||||
|
# Multiply by 10 to convert cm/month --> mm/month
|
||||||
|
pet.append(
|
||||||
|
1.6 * (L / 12.0) * (N / 30.0) * ((10.0 * Ta / I) ** a) * 10.0)
|
||||||
|
|
||||||
|
return pet
|
||||||
|
|
||||||
|
|
||||||
|
def monthly_mean_daylight_hours(latitude, year=None):
|
||||||
|
"""
|
||||||
|
Calculate mean daylight hours for each month of the year for a given
|
||||||
|
latitude.
|
||||||
|
|
||||||
|
:param latitude: Latitude [radians]
|
||||||
|
:param year: Year for the daylight hours are required. The only effect of
|
||||||
|
*year* is to change the number of days in Feb to 29 if it is a leap
|
||||||
|
year. If left as the default, None, then a normal (non-leap) year is
|
||||||
|
assumed.
|
||||||
|
:return: Mean daily daylight hours of each month of a year [hours]
|
||||||
|
:rtype: List of floats.
|
||||||
|
"""
|
||||||
|
_check_latitude_rad(latitude)
|
||||||
|
|
||||||
|
if year is None or not calendar.isleap(year):
|
||||||
|
month_days = _MONTHDAYS
|
||||||
|
else:
|
||||||
|
month_days = _LEAP_MONTHDAYS
|
||||||
|
monthly_mean_dlh = []
|
||||||
|
doy = 1 # Day of the year
|
||||||
|
for mdays in month_days:
|
||||||
|
dlh = 0.0 # Cumulative daylight hours for the month
|
||||||
|
for daynum in range(1, mdays + 1):
|
||||||
|
sd = fao.sol_dec(doy)
|
||||||
|
sha = fao.sunset_hour_angle(latitude, sd)
|
||||||
|
dlh += fao.daylight_hours(sha)
|
||||||
|
doy += 1
|
||||||
|
# Calc mean daylight hours of the month
|
||||||
|
monthly_mean_dlh.append(dlh / mdays)
|
||||||
|
return monthly_mean_dlh
|
||||||
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
Reference in New Issue
Block a user