Bläddra i källkod

补充克里斯计算接口

DIng 3 månader sedan
förälder
incheckning
c7b3d1ecb6
6 ändrade filer med 120 tillägg och 76 borttagningar
  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
 import sqlite3
 from flask import Blueprint, request, jsonify, current_app
 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
 from .model import predict, train_and_save_model, calculate_model_score
 import pandas as pd
 import pandas as pd
 from . import db  # 从 app 包导入 db 实例
 from . import db  # 从 app 包导入 db 实例
@@ -8,11 +11,13 @@ from .database_models import Models, ModelParameters, Datasets, CurrentReduce, C
 import os
 import os
 from .utils import create_dynamic_table, allowed_file, infer_column_types, rename_columns_for_model_predict, \
 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, \
     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
 from sqlalchemy.orm import sessionmaker
 import logging
 import logging
 from sqlalchemy import text, select, MetaData, Table, func
 from sqlalchemy import text, select, MetaData, Table, func
 from .tasks import train_model_task
 from .tasks import train_model_task
+from flask import send_file
+
 
 
 # 配置日志
 # 配置日志
 logging.basicConfig(level=logging.DEBUG)
 logging.basicConfig(level=logging.DEBUG)
@@ -28,7 +33,7 @@ def hash_password(password):
 
 
 def get_db():
 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()
     data = request.get_json()
 
 
     # 打印收到的请求数据
     # 打印收到的请求数据
-    app.logger.info(f"Received data: {data}")
+    bp.logger.info(f"Received data: {data}")
 
 
     user_id = data.get('userId')  # 用户ID
     user_id = data.get('userId')  # 用户ID
     name = data.get('name')  # 用户名
     name = data.get('name')  # 用户名
@@ -1173,7 +1178,7 @@ def import_data():
 
 
     try:
     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)
         file.save(temp_path)
         logger.debug(f"File saved to temporary path: {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:
     finally:
         session.close()
         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.ext.declarative import declarative_base
 from sqlalchemy import Column, Integer, String, Float, DateTime, select, create_engine
 from sqlalchemy import Column, Integer, String, Float, DateTime, select, create_engine
 import uuid
 import uuid
@@ -5,8 +6,7 @@ from datetime import datetime, timezone
 import pandas as pd
 import pandas as pd
 from .database_models import CurrentReduce, CurrentReflux
 from .database_models import CurrentReduce, CurrentReflux
 from sqlalchemy.schema import MetaData, Table
 from sqlalchemy.schema import MetaData, Table
-
-
+import geopandas as gpd
 
 
 Base = declarative_base()
 Base = declarative_base()
 
 
@@ -193,3 +193,36 @@ def predict_to_Q(predictions, init_ph, target_ph):
 # ΔpH=目标pH-初始pH
 # ΔpH=目标pH-初始pH
 def Q_to_t_ha(Q):
 def Q_to_t_ha(Q):
     return Q * 25
     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();
     const now = Date.now();
     if (now - lastTapTime < 1000) return;
     if (now - lastTapTime < 1000) return;
     lastTapTime = now;
     lastTapTime = now;
-    
+  
     const { latitude, longitude } = e.detail;
     const { latitude, longitude } = e.detail;
     
     
     wx.qqmapsdk.reverseGeocoder({
     wx.qqmapsdk.reverseGeocoder({
@@ -179,8 +179,6 @@ Page({
       success: (res) => {
       success: (res) => {
         const address = res.result.address_component;
         const address = res.result.address_component;
         const locationDesc = res.result.location_description || '';
         const locationDesc = res.result.location_description || '';
-  
-        // 多维度判断陆地条件
         const isValidLand = (
         const isValidLand = (
           address.nation === '中国' &&
           address.nation === '中国' &&
           !['香港', '澳门', '台湾'].includes(address.province) &&
           !['香港', '澳门', '台湾'].includes(address.province) &&
@@ -188,75 +186,65 @@ Page({
           !locationDesc.includes('海域') && // 排除描述含海域的关键词
           !locationDesc.includes('海域') && // 排除描述含海域的关键词
           address.district !== '' // 排除无区县信息区域
           address.district !== '' // 排除无区县信息区域
         );
         );
-  
+
         if (isValidLand) {
         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 {
         } else {
-          wx.showToast({ 
-            title: '非有效陆地区域', 
-            icon: 'none' 
-          });
+          wx.showToast({ title: '非有效陆地区域', icon: 'none' });
         }
         }
       },
       },
       fail: (err) => console.error('接口错误:', err)
       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": {},
   "usingComponents": {},
-  "permission": {
-      "scope.userLocation": {
-        "desc": "你的位置信息将用于地图展示"
-      }
-  },
   "navigationBarTitleText": "地图展示",
   "navigationBarTitleText": "地图展示",
   "navigationBarBackgroundColor": "#dbdbdb",  
   "navigationBarBackgroundColor": "#dbdbdb",  
   "navigationBarTextStyle": "black"  
   "navigationBarTextStyle": "black"  

+ 2 - 2
shoping/mapView/mapView.wxss

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