Browse Source

补充克里斯计算接口

DIng 3 months ago
parent
commit
c7b3d1ecb6
6 changed files with 120 additions and 76 deletions
  1. 32 4
      api/app/routes.py
  2. 35 2
      api/app/utils.py
  3. BIN
      api/emissions.xlsx
  4. 51 63
      shoping/mapView/mapView.js
  5. 0 5
      shoping/mapView/mapView.json
  6. 2 2
      shoping/mapView/mapView.wxss

+ 32 - 4
api/app/routes.py

@@ -1,5 +1,8 @@
 import sqlite3
 from flask import Blueprint, request, jsonify, current_app
+from werkzeug.security import generate_password_hash, check_password_hash
+from werkzeug.utils import secure_filename
+from io import BytesIO
 from .model import predict, train_and_save_model, calculate_model_score
 import pandas as pd
 from . import db  # 从 app 包导入 db 实例
@@ -8,11 +11,13 @@ from .database_models import Models, ModelParameters, Datasets, CurrentReduce, C
 import os
 from .utils import create_dynamic_table, allowed_file, infer_column_types, rename_columns_for_model_predict, \
     clean_column_names, rename_columns_for_model, insert_data_into_dynamic_table, insert_data_into_existing_table, \
-    predict_to_Q, Q_to_t_ha
+    predict_to_Q, Q_to_t_ha, create_kriging
 from sqlalchemy.orm import sessionmaker
 import logging
 from sqlalchemy import text, select, MetaData, Table, func
 from .tasks import train_model_task
+from flask import send_file
+
 
 # 配置日志
 logging.basicConfig(level=logging.DEBUG)
@@ -28,7 +33,7 @@ def hash_password(password):
 
 def get_db():
     """ 获取数据库连接 """
-    return sqlite3.connect(app.config['DATABASE'])
+    return sqlite3.connect(bp.config['DATABASE'])
 
 
 # 添加一个新的辅助函数来检查数据集大小并触发训练
@@ -984,7 +989,7 @@ def update_user():
     data = request.get_json()
 
     # 打印收到的请求数据
-    app.logger.info(f"Received data: {data}")
+    bp.logger.info(f"Received data: {data}")
 
     user_id = data.get('userId')  # 用户ID
     name = data.get('name')  # 用户名
@@ -1173,7 +1178,7 @@ def import_data():
 
     try:
         # 保存文件到临时路径
-        temp_path = os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file.filename))
+        temp_path = os.path.join(bp.config['UPLOAD_FOLDER'], secure_filename(file.filename))
         file.save(temp_path)
         logger.debug(f"File saved to temporary path: {temp_path}")
 
@@ -1394,3 +1399,26 @@ def set_current_dataset(data_type, dataset_id):
         
     finally:
         session.close()
+
+
+@bp.route('/kriging_interpolation', methods=['POST'])
+def kriging_interpolation():
+    try:
+        data = request.get_json()
+        required = ['file_name', 'emission_column', 'points']
+        if not all(k in data for k in required):
+            return jsonify({"error": "Missing parameters"}), 400
+
+        # 添加坐标顺序验证
+        points = data['points']
+        if not all(len(pt) == 2 and isinstance(pt[0], (int, float)) for pt in points):
+            return jsonify({"error": "Invalid points format"}), 400
+
+        result = create_kriging(
+            data['file_name'],
+            data['emission_column'],
+            data['points']
+        )
+        return jsonify(result)
+    except Exception as e:
+        return jsonify({"error": str(e)}), 500

+ 35 - 2
api/app/utils.py

@@ -1,3 +1,4 @@
+from pykrige import OrdinaryKriging
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy import Column, Integer, String, Float, DateTime, select, create_engine
 import uuid
@@ -5,8 +6,7 @@ from datetime import datetime, timezone
 import pandas as pd
 from .database_models import CurrentReduce, CurrentReflux
 from sqlalchemy.schema import MetaData, Table
-
-
+import geopandas as gpd
 
 Base = declarative_base()
 
@@ -193,3 +193,36 @@ def predict_to_Q(predictions, init_ph, target_ph):
 # ΔpH=目标pH-初始pH
 def Q_to_t_ha(Q):
     return Q * 25
+
+
+def create_kriging(file_name, emission_column, points):
+    # 从 Excel 读取数据
+    df = pd.read_excel(file_name)
+    print(df)
+
+    # 转换为 GeoDataFrame
+    gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df['longitude'], df['latitude']))
+    print(gdf)
+
+    # 初始化并运行克里金插值
+    OK = OrdinaryKriging(
+        gdf.geometry.x,
+        gdf.geometry.y,
+        gdf[emission_column],
+        variogram_model='spherical',
+        verbose=True,
+        enable_plotting=False
+    )
+
+    # 提取输入点的经度和纬度
+    input_lons = [point[0] for point in points]
+    input_lats = [point[1] for point in points]
+
+    # 对输入的点进行插值
+    z, ss = OK.execute('points', input_lons, input_lats)
+
+    result = {
+        "message": "Kriging interpolation for points completed successfully",
+        "interpolated_concentrations": z.tolist()
+    }
+    return result

BIN
api/emissions.xlsx


+ 51 - 63
shoping/mapView/mapView.js

@@ -171,7 +171,7 @@ Page({
     const now = Date.now();
     if (now - lastTapTime < 1000) return;
     lastTapTime = now;
-    
+  
     const { latitude, longitude } = e.detail;
     
     wx.qqmapsdk.reverseGeocoder({
@@ -179,8 +179,6 @@ Page({
       success: (res) => {
         const address = res.result.address_component;
         const locationDesc = res.result.location_description || '';
-  
-        // 多维度判断陆地条件
         const isValidLand = (
           address.nation === '中国' &&
           !['香港', '澳门', '台湾'].includes(address.province) &&
@@ -188,75 +186,65 @@ Page({
           !locationDesc.includes('海域') && // 排除描述含海域的关键词
           address.district !== '' // 排除无区县信息区域
         );
-  
+
         if (isValidLand) {
-          console.log('有效陆地点击', latitude, longitude);
-          let minDistance = Infinity;
-          let closestPoint = null;
+          wx.showLoading({ title: '计算中...' });
           
-          this.excelData.forEach(item => {
-            const distance = this.calculateDistance(
-              latitude, longitude,
-              item.latitude, item.longitude
-            );
-            
-            if (distance < minDistance) {
-              minDistance = distance;
-              closestPoint = item;
+          // 调用克里金插值接口
+          wx.request({
+            url: 'https://soilgd.com:5000/kriging_interpolation',
+            method: 'POST',
+            header: { 'Content-Type': 'application/json' },
+            data: {
+              file_name: 'emissions.xlsx',
+              emission_column: 'dust_emissions',
+              points: [[longitude, latitude]] // 注意顺序:经度在前
+            },
+            success: (res) => {
+              wx.hideLoading();
+              if (res.statusCode === 200 && res.data.interpolated_concentrations) {
+                const value = res.data.interpolated_concentrations[0];
+                
+                // 创建临时标记
+                const tempMarker = {
+                  id: 0,
+                  latitude,
+                  longitude,
+                  iconPath: "../../assets/taddar/marker.png",
+                  width: 40,
+                  height: 40,
+                  callout: {
+                    content: `经度: ${longitude.toFixed(4)}\n纬度: ${latitude.toFixed(4)}\npH值: ${value.toFixed(2)}`,
+                    display: "ALWAYS",
+                    fontSize: 14,
+                    color: "#333",
+                    bgColor: "#fff",
+                    borderColor: "#07c160",
+                    borderRadius: 8,
+                    borderWidth: 2,
+                    padding: 12,
+                    anchorY: -10
+                  }
+                };
+  
+                // 更新标记数组
+                const markers = this.data.markers
+                  .filter(m => m.id !== 0)
+                  .concat(tempMarker);
+                this.setData({ markers });
+              }
+            },
+            fail: (err) => {
+              wx.hideLoading();
+              wx.showToast({ title: '计算失败', icon: 'none' });
             }
           });
-
-          // 创建临时标记
-          const tempMarker = {
-            id: 0, // 使用特殊ID标识临时标记
-            latitude,
-            longitude,
-            iconPath: "../../assets/taddar/marker.png", // 建议使用不同图标
-            width: 40,
-            height: 40,
-            callout: {
-              content: `经度: ${longitude.toFixed(4)}\n纬度: ${latitude.toFixed(4)}\npH值: ${closestPoint ? closestPoint.dust_emissions.toFixed(2) : '无数据'}`,
-              display: "ALWAYS",
-              fontSize: 14,
-              color: "#333",
-              bgColor: "#fff",
-              borderColor: "#07c160",
-              borderRadius: 8,
-              borderWidth: 2,
-              padding: 12,
-              anchorY: -10
-            }
-          };
-
-          // 更新标记数组(保留原有标记,替换临时标记)
-          const markers = this.data.markers
-            .filter(m => m.id !== 0)
-            .concat(tempMarker);
-
-          this.setData({ markers });
         } else {
-          wx.showToast({ 
-            title: '非有效陆地区域', 
-            icon: 'none' 
-          });
+          wx.showToast({ title: '非有效陆地区域', icon: 'none' });
         }
       },
       fail: (err) => console.error('接口错误:', err)
     });
   },
 
-  // 新增距离计算方法
-  calculateDistance(lat1, lng1, lat2, lng2) {
-    const rad = (angle) => angle * Math.PI / 180;
-    const R = 6371; // 地球半径(千米)
-    
-    const dLat = rad(lat2 - lat1);
-    const dLng = rad(lng2 - lng1);
-    
-    const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
-              Math.cos(rad(lat1)) * Math.cos(rad(lat2)) *
-              Math.sin(dLng/2) * Math.sin(dLng/2);
-    
-    return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
-  },
 });

+ 0 - 5
shoping/mapView/mapView.json

@@ -1,10 +1,5 @@
 {
   "usingComponents": {},
-  "permission": {
-      "scope.userLocation": {
-        "desc": "你的位置信息将用于地图展示"
-      }
-  },
   "navigationBarTitleText": "地图展示",
   "navigationBarBackgroundColor": "#dbdbdb",  
   "navigationBarTextStyle": "black"  

+ 2 - 2
shoping/mapView/mapView.wxss

@@ -1,8 +1,8 @@
 .switch-container {
   display: flex;
   align-items: center;
-  justify-content: center;
-  margin-top: 20px;
+  padding: 10px;
+  border-bottom: 1px solid #eee;
 }
 
 .switch-container text {