简配GIS 网页显示

容器中使用图片地图全覆盖

JSON文件加载,各个点位相对图片容器位置创建可点击点位,可点击点位扩大10px创建点击事件(直接用css标签 然后来实现),调用模态框,可以添加AJAX事件实现交互。

循环上面步骤 大地图嵌套模态框显示小地图,或者显示点击交互事件,小地图也可以在上面创建交互实现地图的联合交互创建。

地图的相对点位如何创建?

下面列子非常好显示 了其中内容,创建一个图片容器div,1920×1080 大小,不可缩放。

图片100%铺满,通过each 以及定义好的css 创建点击位置,和方法

区域定位.css({left:item.x,top:item.y})

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<style>
.map-box{
  position:relative;
  width:1920px;
  height:1080px;
  border:1px solid #999;
}
.factory-map{
  width:1920px;
  height:1080px;
  display:block;
}
body{
  overflow:auto;
}
/* 热区:宽高32px,四周多出10px点击范围  用于创建点击事件 巧妙*/ 
.point-hot{
  position:absolute;
  width:32px;
  height:32px;
  transform:translate(-50%,-50%);
  cursor:pointer;
  background:transparent;
}
.point-dot{
  width:12px;
  height:12px;
  background:#f33;
  border-radius:50%;
  margin:10px;
}
.point-text{
  position:absolute;
  margin-top:-18px;
  font-size:12px;
  white-space:nowrap;
}
</style>
</head>
<body>
<div class="map-box" id="mapBox">
    <img class="factory-map" src="factory.jpg">
</div>
<script>
$(function(){
    let pointList = [
        {x:300,y:220,name:"一号车间"},
        {x:650,y:480,name:"水泵房"}
    ];
    const $mapBox = $("#mapBox");

    $.each(pointList,function(i,item){
        let $hot = $("<div class='point-hot'></div>")
            .css({left:item.x,top:item.y})
            .append("<div class='point-dot'></div><div class='point-text'>"+item.name+"</div>");
        
        // 点击热区任意位置,弹出点位名称
        $hot.click(function(e){
            e.stopPropagation();
            alert(item.name);
        });
        $mapBox.append($hot);
    })

    // 空白区域点击只打印坐标,不弹窗
    $mapBox.click(function(e){
        let r = $mapBox[0].getBoundingClientRect();
        let x = e.clientX - r.left,y = e.clientY - r.top;
        console.log("X:"+parseInt(x)+" Y:"+parseInt(y));
    })
})
</script>
</body>
</html>

创建区域

通过CSS来确定显示的样式

#CSS
/* 全局热力覆盖图层,底层、鼠标穿透不干扰点击 */
.heat-layer{
  position:absolute;
  left:0;
  top:0;
  width:100%;
  height:100%;
  z-index:2; /* 固定在厂房下方,底图上方 */
  pointer-events:none; /* 关键:色块不拦截鼠标点击厂房/传感器 */
  display:none;
}
/* 单块厂房热力色块 */
.heat-block{
  position:absolute;
  filter: blur(12px); /* 色块之间柔和渐变融合 */
  border-radius:6px;
}

创建区域 【显示区域使用添加DIV中加入上面的CSS样式,后用js读取json厂区的宽高后设置宽高 】区域定位参考上面,宽高定位width high

// 循环所有分区
parkConfig.zones.forEach((zone,zoneIdx)=>{
  // 循环当前分区全部厂房
  zone.factories.forEach((fac,facIdx)=>{
    // 厂房矩形宽高、坐标
    const tw = fac.x2 - fac.x1;
    const th = fac.y2 - fac.y1;

    // 根据业务温度获取厂房对应颜色
    function getTempColor(temp){
      if(temp<20)return "#2196F3";
      else if(temp>30)return "#f44336";
      return "#4CAF50";
    }
    // 这里简化统一厂房底色,你可替换为区域平均温度色
    const areaColor = "#0066ff";

    // 创建厂房热力覆盖色块,放入独立热力图层
    let $heat = $(`<div class="heat-block"></div>`)
      .css({
        left: fac.x1,
        top: fac.y1,
        width: tw,
        height: th,
        background: areaColor,
        opacity: 0.28 // 透明度,不遮挡底图
      })
    // 追加到热力覆盖图层
    $heatLayer.append($heat);
  })
})

点击事件及点击后触发

使用AJAX去后台读取内容,使用模态框来显示回传信息,可以在其内部添加一个请求的url【包含请求信息】通过GET请求回传JSON信息。

实时点位显示

同步动态点位信息,比如巡检ID显示,直接定期AJAX请求,然后显示点,可以在顶部显示z-index,数值高。依然适用ajax点击事件显示。(也可以直接展示信息)

跟正点击信息

A 直接URL 通过AJAX后请求后模态框展示信息

B 直接展示信息,直接模态框展示信息

doing中JSON显示其中 有url和 info两个参数分别去读取。

热力图

一、CSS 部分(分 3 块:画布基础、热力容器、圆形热力块)

1. 画布基础容器(固定 1900×900,承载底图 + 所有图层)

css

.map-wrap{
  position:relative;
  width:1900px;
  height:900px;
  border:2px solid #666;
  overflow:auto;
}
#bgImg{
  position:absolute;
  left:0;top:0;
  width:1900px;height:900px;
  object-fit:cover;
  display:block;
}

作用:

  • 固定画布尺寸 1900 宽 900 高,所有坐标基于这个画布;
  • position:relative 给内部热力、厂房、点位提供定位基准。

2. 热力总容器 .heat-layer(整层统一控制)

css

.heat-layer{
  position:absolute;
  left:0;
  top:0;
  width:100%;
  height:100%;
  z-index:2; /* 层级:底图之上、厂房之下 */
  pointer-events:none; /* 核心:鼠标穿透,不挡住点击厂房/传感器 */
  display:none; /* 默认隐藏,开关按钮控制显示 */
}

关键属性解释:

  1. position:absolute:铺满整个画布;
  2. z-index:2 图层顺序:底图 (0) < 热力层 (2) < 厂房 (4) < 传感器 (8);
  3. pointer-events:none:鼠标操作会直接穿过热力色块,底层交互完全不受影响;
  4. display:none:一键开关只改这个容器的 display,不用删所有色块。

3. 单个圆形热力块 .heat-block(核心实现圆形 + 渐变融合)

css

.heat-block{
  position:absolute;
  border-radius:50%; /* 强制正方形div变成正圆形 */
  filter: blur(20px); /* 高斯模糊,让圆形边缘向外虚化,多个圆融合渐变 */
}

两条核心:

  1. border-radius:50%:宽高相等的 div,圆角 50% = 标准圆形;
  2. filter: blur(20px):圆形边缘向外扩散虚化 20 像素,相邻圆形色块重叠后自动形成平滑过渡,这就是你看到整片渐变的来源;
  • 如果去掉 blur(20px),就能清晰看到一个个独立圆形热源。

二、JS 分步拆解(只看热力相关逻辑,分 5 步)

前置说明

数据结构:parkConfig.zones[0].sensors独立传感器数组,每个传感器包含 x,y,temp,热源只来源于这些点位。

步骤 1:工具函数 —— 根据温度返回热力颜色

js

运行

// 输入温度,返回半透明RGBA颜色
function tempToHeatColor(temp){
  if(temp <= 20) return "rgba(33,150,243,0.28)";  // 低温蓝色
  if(temp <= 30) return "rgba(76,175,80,0.28)";   // 常温绿色
  return "rgba(244,67,54,0.28)";                  // 高温红色
}

末尾 0.28 = 色块透明度,数值越低底色越淡,能看清底下厂区底图。

步骤 2:热力渲染主函数 renderHeatMap()(循环生成所有圆形色块)

js

运行

function renderHeatMap(){
  // 第一步:清空上一轮所有热力圆形,防止重复叠加
  $(".heat-block").remove();
  const $heatLayer = $("#heatLayer");

  // 遍历所有园区分区(当前只有1个默认分区)
  parkConfig.zones.forEach(zone=>{
    // 遍历【独立传感器数组】,每一个传感器生成1个圆形热力块
    zone.sensors.forEach(sen=>{
      // 1.获取当前传感器对应的温度颜色
      const fillColor = tempToHeatColor(sen.temp);

      // 2.根据温度计算圆形扩散半径:温度越高,圆越大
      const spreadRadius = Math.max(40, Math.min(130, sen.temp * 2.3));
      // Math.max(40,xxx):最小半径40,不会过小看不见
      // Math.min(130,xxx):最大半径130,不会铺满整张图

      // 3.创建圆形DOM热力块,定位到传感器坐标中心
      $("<div class='heat-block'></div>")
      .css({
        // 圆心在sen.x sen.y,向左/上偏移半径,保证圆形中心对准传感器点位
        left: sen.x - spreadRadius,
        top: sen.y - spreadRadius,
        // 宽高 = 两倍半径,形成完整圆形
        width: spreadRadius * 2,
        height: spreadRadius * 2,
        background: fillColor
      })
      // 把圆形色块追加到热力总容器内
      .appendTo($heatLayer);
    })
  })
}

坐标逻辑重点

  • 传感器坐标 sen.x sen.y 是圆形圆心
  • 圆形宽高 2*spreadRadius
  • left = 圆心x - 半径top = 圆心y - 半径,才能让圆形中心精准落在传感器绿点上。

步骤 3:全局渲染入口,每次重绘自动刷新热力

js

运行

function renderAll(){
  clearRender(); // 清空厂房、点位、旧热力
  // 渲染分区、厂房、传感器红点(原有逻辑省略)
  renderHeatMap(); // 最后执行热力圆形生成,刷新整张温度图层
}

触发时机:新建 / 删除传感器、导入 JSON、修改温度后,执行 renderAll(),热力自动重新绘制。

步骤 4:热力图层开关按钮逻辑(控制整层显示隐藏)

js

运行

let heatShow = false;
$("#toggleHeat").click(function(){
  heatShow = !heatShow;
  // 只修改热力总容器display,所有圆形色块一起显示/隐藏
  $heatLayer.css("display", heatShow ? "block" : "none");
  $(this).text(heatShow ? "关闭温度热力图层" : "开启温度热力图层");
})

不用逐个删除圆形 DOM,只控制外层容器,性能更高。

步骤 5:补充:关闭模糊,看清原生圆形(可选按钮逻辑)

新增变量控制模糊,切换后能清晰看到单个圆形:

js

运行

let heatBlurOn = true;
$("#toggleBlur").click(function(){
  heatBlurOn = !heatBlurOn;
  renderAll();
})
// 修改renderHeatMap内css赋值,动态控制blur
let cssStyle = {
  left: sen.x - spreadRadius,
  top: sen.y - spreadRadius,
  width: spreadRadius * 2,
  height: spreadRadius * 2,
  background: fillColor
};
if(heatBlurOn){
  cssStyle.filter = "blur(20px)";
}
$("<div class='heat-block'></div>").css(cssStyle).appendTo($heatLayer);
  • heatBlurOn=true:开启模糊,圆形边缘融合,看不出单独圆形;
  • heatBlurOn=false:无模糊,每个热源都是清晰独立圆形。

三、完整运行逻辑流程(从头到尾执行顺序)

  1. 上传厂区图片,填充 #bgImg 作为画布底图;
  2. 用户点击「绘制传感器点位」,在画布点击生成传感器,存入 zone.sensors
  3. 点击「开关热力图层」,heatShow=true.heat-layer 变为 display:block
  4. 页面执行 renderAll() → 调用 renderHeatMap()
  5. JS 循环读取所有独立传感器 x/y/temp
  6. 根据温度计算圆形半径、半透明颜色;
  7. 批量创建带 border-radius:50% 的圆形 div,放入热力容器;
  8. CSS blur(20px) 虚化所有圆形边缘,色块互相叠加融合,形成整张平滑温度图层;
  9. 热力层 pointer-events:none,底层厂房、传感器删除、弹窗功能完全不受遮挡。

四、你疑惑 “看不到圆形” 的根本原因

  1. 每个热源确实是标准圆形 div;
  2. 代码默认开启 filter: blur(20px),圆形边缘向外大面积虚化扩散;
  3. 多个圆形虚化区域重叠、颜色混合,肉眼看不到单独圆形轮廓,只看到整片渐变;
  4. 关闭 blur 模糊后,立刻能看到一个个独立圆形热源。
发表在 None | 留下评论

温湿度传感器 气压 气体

BME680

from bme680 import *
from machine import I2C, Pin
import time

bme = BME680_I2C(I2C(-1, scl=Pin(13), sda=Pin(14)))
print(bme.temperature, bme.humidity, bme.pressure, bme.gas)

关闭电加热丝 可以减少2摄氏度误差,修改源码bme680.py

        # gas measurements enabled
        #self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS])
        # gas measurements disabled
        self._write(_BME680_REG_CTRL_GAS, [0x00])

bmp280 AHT20 联合传感器 (温湿度)

from machine import I2C, Pin
import time
import ahtx0
from bmp280 import *



i2c=I2C(-1, scl=Pin(14), sda=Pin(13))
sensor = ahtx0.AHT10(i2c)
bmp = BMP280(i2c,addr=0x77)

#气压 温度 湿度
bmp.pressure
sensor.temperature
sensor.relative_humidity

资料

https://gitee.com/mirrors_robert-hh/bme680-micropython

BME680 全包

后期

BMP280+AHT20

SGP-41 氮氧化合物 CO气体检测

发表在 None | 留下评论

蓝牙

广播 100ms 间隔高速率

import bluetooth, struct


#汉字一个3B,注意 最大8个汉字  100ms 广播高速率
def ble_name(dev_name):
    if len(dev_name)>26:
        return False
    ble = bluetooth.BLE()
    ble.active(False)
    ble.active(True)
    adv_data = struct.pack('BBB', 0x02, 0x01, 0x06)
    name_bytes = dev_name.encode()
    adv_data += struct.pack('B', 1 + len(name_bytes)) + b'\x09' + name_bytes
    ble.gap_advertise(None)
    ble.gap_advertise(100000, adv_data)
    print("广播启动,名称:", dev_name," 长度:",len(dev_name))
    return True

扫描 1s 定时版

from micropython import const
import bluetooth
import time

# 常量定义
_IRQ_SCAN_RESULT = const(5)
AD_TYPE_NAME_COMPLETE = const(0x09)
AD_TYPE_NAME_SHORT = const(0x08)

ble = bluetooth.BLE()
device_set = set()

def _parse_name(adv_data):
    i = 0
    while i < len(adv_data):
        ad_len = adv_data[i]
        if ad_len == 0:
            break
        ad_type = adv_data[i+1]
        payload = adv_data[i+2:i+1+ad_len]
        if ad_type in (AD_TYPE_NAME_COMPLETE, AD_TYPE_NAME_SHORT):
            try:
                return payload.decode("utf-8").strip()
            except:
                return "Unreadable"
        i += ad_len + 1
    return "NoName"

def _scan_callback(event, data):
    global device_set
    if event == _IRQ_SCAN_RESULT:
        _, _, _, _, adv_data = data
        name = _parse_name(bytes(adv_data))
        device_set.add(name)

def scan_ble_devices():
    """
    阻塞扫描1秒,返回周围蓝牙设备名称列表(去重)
    return: list[str]
    """
    global device_set
    device_set.clear()
    # 初始化蓝牙与回调
    ble.active(True)
    ble.irq(_scan_callback)
    # 扫描时长1000ms,高速抓包参数
    ble.gap_scan(1000, 30000, 28000, 1)
    # 阻塞等待最多1秒
    t_start = time.ticks_ms()
    while time.ticks_diff(time.ticks_ms(), t_start) < 1000:
        pass
    # 停止扫描、关闭蓝牙
    ble.gap_scan(None)
    ble.active(False)
    # 转列表返回
    return list(device_set)


# 调用示例
if __name__ == "__main__":
    names = scan_ble_devices()
    print("扫描到蓝牙设备:")
    for n in names:
        print("-", n)

#返回
#names = scan_ble_devices()

后台版本

开启服务 start_background_full_scan()然后返回扫描的设备列表 scan_ble_devices()

from micropython import const
import bluetooth
import time

# BLE常量
_IRQ_SCAN_RESULT = const(5)
AD_TYPE_NAME_COMPLETE = const(0x09)
AD_TYPE_NAME_SHORT = const(0x08)

ble = bluetooth.BLE()
# 存储结构 {设备名称: 最后收到广播时间戳}
dev_cache = dict()
SCAN_RUNNING = False
# 设备超时时间:1500ms未收到广播判定离开
DEV_TIMEOUT = 1500

def _parse_name(adv_data):
    """解析广播包中的蓝牙设备名称"""
    idx = 0
    data_len = len(adv_data)
    while idx < data_len:
        ad_len = adv_data[idx]
        if ad_len == 0:
            break
        ad_type = adv_data[idx + 1]
        payload = adv_data[idx + 2 : idx + 1 + ad_len]
        if ad_type in (AD_TYPE_NAME_COMPLETE, AD_TYPE_NAME_SHORT):
            try:
                return payload.decode("utf-8").strip()
            except Exception:
                return "Unreadable_Device"
        idx += ad_len + 1
    return "No_Name"

def ble_irq_callback(event, data):
    global dev_cache
    if event == _IRQ_SCAN_RESULT:
        _, _, _, _, adv_data = data
        dev_name = _parse_name(bytes(adv_data))
        now = time.ticks_ms()
        # 更新该设备最后活跃时间
        dev_cache[dev_name] = now

def start_background_full_scan():
    """启动后台永久高速扫描,匹配100ms间隔信标"""
    global SCAN_RUNNING
    if SCAN_RUNNING:
        return
    ble.active(True)
    ble.irq(ble_irq_callback)
    # gap_scan(永久扫描0, 扫描周期100000us=100ms, 监听窗口90000us=90ms, 主动扫描1)
    ble.gap_scan(0, 100000, 90000, 1)
    SCAN_RUNNING = True
    print("后台BLE扫描已启动 | 适配100ms间隔蓝牙信标")

def scan_ble_devices():
    """
    对外调用接口:瞬间返回当前在场蓝牙设备名称列表
    自动清理超时消失的设备,无阻塞
    return: list[str]
    """
    global dev_cache
    if not SCAN_RUNNING:
        return []
    now = time.ticks_ms()
    # 过滤超时设备
    valid_devs = []
    for name, last_tick in dev_cache.items():
        if time.ticks_diff(now, last_tick) < DEV_TIMEOUT:
            valid_devs.append(name)
    return valid_devs

def stop_ble_scan():
    """停止扫描、关闭蓝牙,程序结束时调用"""
    global SCAN_RUNNING
    if not SCAN_RUNNING:
        return
    ble.gap_scan(None)
    ble.active(False)
    SCAN_RUNNING = False
    dev_cache.clear()
    print("BLE扫描已关闭")

------------------- 测试示例 -------------------
if __name__ == "__main__":
    # 程序启动只执行一次,开启后台扫描
    start_background_full_scan()

    # 循环读取设备列表,随便高频调用无压力
    while True:
        dev_list = scan_ble_devices()
        print("==== 在场蓝牙设备 ====")
        if len(dev_list) == 0:
            print("无设备")
        else:
            for name in dev_list:
                print("-", name)
#         time.sleep_ms(500)

发表在 None | 留下评论

技术表

  • 蓝牙 广播(自定义31B)+扫描
  • lora 传输(大文件传输大于255B、中继)
  • E-INK显示
  • 按键IRQ
  • 温湿度传感器
  • 气体传感器
  • 24GHZ雷达检测人员
  • 录音模块
  • TTS模块(TTL)
发表在 None | 留下评论

thinkphp 分页 service化 终章

前端:

    <div class="row text-center">
        <div class="pagination">{$info->render()|raw}</div>
        <p class="text-muted">---共计<b class="text-danger">{$info->total()}</b>条---</p>
    </div>

控制器

    public function list(Pt12345Service $Pt12345Service){
        $status=$this->request->get('status/d',0);
        $info=['status'=>$status];
        $list=$Pt12345Service->search($info);

        #查询结果中将查询条件打包带入
                 $list->appends($this->request->param());

        $department=$Pt12345Service->department();
        $this->assign(['info'=>$list,"department"=>$department]);
        return $this->fetch();
    }

service

    public function search($info){
        $where=[];
        if(isset($info['status'])){
            if($info['status']<10){
                $where[]=['status','=',$info['status']];
            }
        }
        $list = Pt12345Model::where($where)
            ->order('id desc')
            ->paginate(20, false, ['query' => $info]);
#使用paginate分页,代替select,并且false,之后query内容未传入的$info变量
        return $list;
    }
发表在 None | 留下评论