基于webGIS的大气监测系统设计

项目地址

gitee项目地址

项目介绍

背景介绍

随着工业化和城市化的快速发展,大气环境问题愈发严重,对人类健康和社会可持续发展构成了严重威胁。为了有效应对这一挑战,本文设计并实现了一个基于WebGIS的大气环境监测系统。该系统集成了WebGIS技术和可视化手段,为大气环境的管理、监测与预测提供了高效、便捷的工具和方法。
本系统基于WebGIS技术,采用B/S架构进行开发,旨在实现大气环境监测信息的高效管理和数据分析。系统通过实时采集大气监测站点的数据,并结合地理信息系统(GIS)技术,以地图形式直观地展示大气环境信息,从而实现对大气污染物的可视化监测。此外,系统还具备污染物预测功能,能够根据历史数据和当前环境参数,预测未来一段时间内污染物的浓度变化。
在空气质量指数的预测方面,考虑到现有预测模型往往忽视污染物浓度与空气质量指数之间的关联性以及地区差异性,本文提出了一种基于历史污染物浓度间接预测空气质量指数的新方法。该方法是对污染物浓度预测后计算AQI指数,能够将指数误差平均到每个污染物浓度中,从而减少误差。通过对比模型预测误差验证了该方法的可行性。同时,为解决地区差异性过大的问题,系统提供自定义模型训练功能,针对不同地区构建不同模型。

技术介绍

技术架构
前端方面包括:

html+css+js三件套
Vue3.0+Axios+Arcgis Api for Javascript+Echarts

后端方面包括:

权限管理:Sa-Token
工具包:Hutool
数据导出:EasyExcel
深度学习:Deeplearning4j
持久层:MybatisPlus

功能介绍

功能模块
具体功能模块如上所示

项目展示

界面展示

登录注册页面:
登录注册页面
监控中心页面
监控中心
在这里插入图片描述
在这里插入图片描述
后台管理页面:
在这里插入图片描述

核心功能展示

空间插值结果:
在这里插入图片描述
动态任务修改:
在这里插入图片描述
动态任务管理:在这里插入图片描述
数据迁移:
在这里插入图片描述
数据导出
在这里插入图片描述
模型训练:
在这里插入图片描述
模型预测:
在这里插入图片描述
模型关联:
在这里插入图片描述

项目亮点

1. 扩展SpringBoot定时任务模块自定义实现动态时间设置,动态任务的启动与关闭,避免引入第三方扩展增加复杂性。

package org.nimi317.web_gis.task;

import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.nimi317.web_gis.entity.Job;
import org.nimi317.web_gis.service.IJobService;
import org.nimi317.web_gis.service.IUpdateService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.config.Task;
import org.springframework.scheduling.config.TriggerTask;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author thunderobot
 */
@Component
@Slf4j
public class ScheduledTask implements SchedulingConfigurer {

    private ScheduledTaskRegistrar registrar;

    private final IJobService jobService;

    private final ApplicationContext context;

    private final ThreadPoolTaskExecutor executor;

    private final IUpdateService service;

    private final AirQualityRealTask task;

    private final Map<Integer, org.springframework.scheduling.config.ScheduledTask> taskMap = new LinkedHashMap<>();

    public ScheduledTask(@Lazy IJobService jobService, @Lazy ApplicationContext context, ThreadPoolTaskExecutor executor, IUpdateService service, @Lazy AirQualityRealTask task) {
        this.jobService = jobService;
        this.context = context;
        this.executor = executor;
        this.service = service;
        this.task = task;
    }

    @Override
    public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
        this.registrar = taskRegistrar;
        this.init();
        log.info("初始化定时任务");
        this.after();
    }

    private void init() {
        List<Job> jobs = jobService.getAllJobs();
        for (Job job : jobs) {
            add(job.getId(), getTask(job));
        }
    }

    private TriggerTask getTask(Job job) {
        Object bean = context.getBean(job.getBean());
        Integer id = job.getId();
        return new TriggerTask(() -> {
            try {
                Method method = bean.getClass().getMethod(job.getMethod());
                method.setAccessible(true);
                executor.execute(() -> {
                    try {
                        method.invoke(bean);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                });
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }, triggerContext -> new CronTrigger(jobService.getJobById(id).getCron()).nextExecution(triggerContext));
    }


    /**
     * 添加定时任务
     */
    public void add(int id, TriggerTask task) {
        //如果已经存在 则暂停之前的
        org.springframework.scheduling.config.ScheduledTask before = this.taskMap.get(id);
        if (before != null) {
            before.cancel(false);
        }
        org.springframework.scheduling.config.ScheduledTask scheduledTask = this.registrar.scheduleTriggerTask(task);
        this.taskMap.put(id, scheduledTask);
        log.info("定时任务已添加, id:{}", id);
    }

    /**
     * 启动定时任务
     */
    public void start(int id) {
        org.springframework.scheduling.config.ScheduledTask task = this.taskMap.get(id);
        if (task != null) {
            Task taskTask = task.getTask();
            add(id, (TriggerTask) taskTask);
        } else {
            Job job = this.jobService.getJobById(id);
            add(job.getId(), getTask(job));
        }
        log.info("定时任务启动,id:{}", id);
    }

    /**
     * 停止定时任务
     */
    public void stop(int id) {
        org.springframework.scheduling.config.ScheduledTask task = this.taskMap.get(id);
        if (task != null) {
            task.cancel(false);
        }
        log.info("定时任务停止,id:{}", id);
    }

    public void remove(int id) {
        stop(id);
        this.taskMap.remove(id);
        log.info("定时任务移除,id:{}", id);
    }

    private void after() {
        if (!service.isUpdated()) {
            task.setUpdate(false);
            task.task();
        } else {
            task.setUpdate(true);
        }
    }
}

2. 借助浏览器Worker技术解决空间插值大量计算导致卡顿问题,提升流畅度。

import {train} from "@sakitam-gis/kriging";
import {interpolatePoly} from "@/assets/interpolate.ts";
import {grid_, IDW, idw_grid} from "@/utils/interpolate.ts";

onmessage = (e) => {
    const {status} = e.data
    status_[status](e)
}

const status_ = {
    0: kriging,
    1: idw
}

function kriging(e) {
    const {data, params, width, pipList, obj, id} = e.data
    // 训练模型
    let t = data.map(x => x.z);
    let x = data.map(x => x.x);
    let y = data.map(x => x.y);
    // 当我想要获取process对象 不需要对象而是直接使用方法
    process(id, "开始训练数据")
    let model = train(t, x, y, params.model, params.sigma2, params.alpha);
    process(id, "开始进行插值")
    let gridInfo = grid_(interpolatePoly, model, width, pipList);
    process(id, "插值完成")
    post({status: 0, gridInfo}, e)
}

function idw(e) {
    const {data, pipList, width, power, obj, distance, id} = e.data
    let values = data.map(x => x.values);
    let positions = data.map(x => x.positions);
    const idw = new IDW({positions, values});
    idw[`use${distance}`]()
    process(id, "开始进行插值")
    let grid = idw_grid(interpolatePoly, power, width, pipList, idw);
    let zlim = values.reduce((prev, curr) => {
        if (prev[0] > curr) prev[0] = curr;
        if (prev[1] < curr) prev[1] = curr;
        return prev;
    }, [Infinity, -Infinity])
    process(id, "插值完成")
    let gridInfo = Object.assign({}, grid, {zlim})
    post({gridInfo, status: 1}, e)
}

function post(args, e) {
    self.postMessage({...args, id: e.data.id, obj: e.data.obj})
}

function process(id, info) {
    self.postMessage({
        status: 2,
        id: id,
        method: "process",
        info: info
    })
}

3. 使用自定义线程池+阻塞队列实现固定大小模型训练任务,并通过Deeplearning4j神经网络模块构建模型。

package org.nimi317.web_gis.utils;

import cn.hutool.core.io.IoUtil;
import lombok.SneakyThrows;
import org.nimi317.web_gis.exception.E;
import org.nimi317.web_gis.exception.RException;
import org.nimi317.web_gis.form.post.ModelPost;
import org.nimi317.web_gis.runnable.ModelRunnable;
import org.nimi317.web_gis.service.ModelService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author thunderobot
 */
@Component
public class ModelUtils {

    private final ModelService modelService;

    private final static String basePath = "C:\\Users\\thunderobot\\Desktop\\毕业设计\\web_gis\\src\\main\\resources\\data";


    private final BlockingQueue<Runnable> trainingQueue;

    private final ModelRunnable modelRunnable;


    public ModelUtils(ModelService modelService, @Value("${model.maxWaitingSize}") Integer maxWaitingSize,
                      @Value("${model.numThreads}") Integer numThreads, @Lazy ModelRunnable modelRunnable) {
        this.modelService = modelService;
        // 创建一个具有最大等待队列大小的BlockingQueue
        this.trainingQueue = new ArrayBlockingQueue<>(maxWaitingSize == null ? 5 : maxWaitingSize);
        // 创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(numThreads == null ? 1 : numThreads);
        this.modelRunnable = modelRunnable;

        // 提交一个任务到线程池,它不断从BlockingQueue中获取任务并执行
        executorService.submit(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    // 从队列中获取任务,如果队列为空则阻塞
                    Runnable task = trainingQueue.take();
                    // 执行任务
                    task.run();
                } catch (InterruptedException e) {
                    // 线程被中断,退出循环
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
    }

    // 提交一个训练任务
    public boolean submitTrainingTask(Runnable trainingTask) {
        // 尝试将任务放入队列,如果队列已满则返回false
        return trainingQueue.offer(trainingTask);
    }

    /**
     * 保存csv文件
     */
    public String saveCsv(MultipartFile file) {
        String s = file.getOriginalFilename().split("\\.")[1];
        if (!"csv".equals(s)) {
            throw new RException(E.FileException);
        }
        String fileName = UUID.randomUUID() + ".csv";
        String out = basePath + "\\" + fileName;
        save(file, out);
        return out;
    }

    @SneakyThrows
    public void save(MultipartFile file, String out) {
        File newFile = new File(out);
        //检查如果路径文件夹不存在则创建
        File parentFile = newFile.getParentFile();
        if (!parentFile.exists()) {
            parentFile.mkdirs();
        }
        FileOutputStream stream = new FileOutputStream(newFile);
        stream.write(file.getBytes());
        IoUtil.close(stream);
    }

    public boolean train(Integer id, String url, ModelPost post) {
        return this.submitTrainingTask(() -> {
            try{
                modelRunnable.run(id, url, post);
            }catch (Exception e) {
                modelService.handleError(id);
            }
        });
    }
}

4. 自定义实现应用层的简单分表查询,通过实现MybatisPlus拦截器对sql拦截根据时间范围进行动态sql修改

package org.nimi317.web_gis.interceptor;

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.TableNameParser;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.nimi317.web_gis.division.DivisionInterface;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

import static org.nimi317.web_gis.data.AreaCodeList.getStrategy;

/**
 * @author thunderobot
 * 分表
 */
@Slf4j
public class MybatisInterceptor implements InnerInterceptor {

    private void extracted(PluginUtils.MPBoundSql boundSql) {
        String sql = boundSql.sql();
        TableNameParser tableNameParser = new TableNameParser(sql);
        /*
            当前的表名
         */
        List<TableNameParser.SqlToken> names = new ArrayList<>();
        tableNameParser.accept(names::add);
        /*
         * 获取分表策略
         */
        StringBuilder builder = new StringBuilder();
        int last = 0;
        for (TableNameParser.SqlToken name : names) {
            String value = name.getValue();
            DivisionInterface strategy = getStrategy(value);
            String s = ObjectUtils.isEmpty(strategy) ? value : strategy.divisionTableName(value, sql, name,boundSql);
            int start = name.getStart();
            if (start != last) {
                builder.append(sql, last, start);
                builder.append(s != null ? s : value);
            }
            last = name.getEnd();
        }
        if (last != sql.length()) {
            builder.append(sql.substring(last));
        }
        String string = builder.toString();
        boundSql.sql(string);
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.SELECT || sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            if (InterceptorIgnoreHelper.willIgnoreDynamicTableName(ms.getId())) {
                return;
            }
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            this.extracted(mpBs);
        }
    }
}

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值