Ver código fonte

功图采集处理增加功图诊断、功图量液

chenwen 1 ano atrás
pai
commit
e077b4bee9

+ 14 - 0
pom.xml

@@ -85,6 +85,20 @@
             <artifactId>netty-all</artifactId>
             <version>${netty-all.version}</version>
         </dependency>
+        
+        <dependency>
+		  <groupId>commons-fileupload</groupId>
+		  <artifactId>commons-fileupload</artifactId>
+		  <version>1.3</version>
+	   </dependency>
+	   
+         <dependency>
+            <groupId>org.opencv</groupId>
+            <artifactId>opencv</artifactId>
+            <version>4.8.0</version>
+            <scope>system</scope>
+            <systemPath>D:\workspace\springboot3\zl-opd-server\lib\opencv-480.jar</systemPath>
+        </dependency>
 
   </dependencies>
 	

+ 7 - 0
src/main/java/com/hb/proj/api/controller/APIController.java

@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RestController;
 import com.hb.proj.gather.model.GatherConfig;
 import com.hb.proj.gather.model.LiquidParam;
 import com.hb.proj.gather.process.DataTransConfig;
+import com.hb.proj.gather.process.diagnose.DiagramDiagnoseFacade;
 import com.hb.proj.gather.protocol.ChannelGroupMgr;
 import com.hb.proj.gather.protocol.ZLOpdProtCMDEnum;
 import com.hb.proj.gather.rep.RedisRepComponent;
@@ -38,6 +39,12 @@ public class APIController {
 	
 	@Autowired
 	private GatherScheduler gatherSche;
+	
+	@RequestMapping("/test")
+	public RespVO<Object> test(){
+		DiagramDiagnoseFacade.createDiagramChart("D:/", null);
+		return RespVOBuilder.ok();
+	}
 
 	/**
 	 * 手动发送指令(只针对50188设备内部测试用)

+ 1 - 1
src/main/java/com/hb/proj/gather/process/DataTransRepSingleTask.java

@@ -78,7 +78,7 @@ public class DataTransRepSingleTask implements Runnable{
 			repService.save(mappingDatas.values());  //入库
 			repService.saveAlarm(almlogs);
 			
-			logger.info("单值数据转换、报警、入库完成:{}",gatherDatas.size());
+			logger.info("单值数据转换、报警、入库完成:{}",singleCombPO.getDevSerial());
 		}
 		catch(Exception e) {
 			e.printStackTrace();

+ 7 - 0
src/main/java/com/hb/proj/gather/process/DataTransRepTask.java

@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
 
 import com.hb.proj.gather.model.DiagramPO;
 import com.hb.proj.gather.model.WellParamVO;
+import com.hb.proj.gather.process.diagnose.DiagramDiagnoseFacade;
 import com.hb.proj.gather.rep.GatherDataRepService;
 import com.hb.proj.gather.rep.RedisRepComponent;
 import com.hb.xframework.util.ApplicationContextUtils;
@@ -49,6 +50,12 @@ public class DataTransRepTask implements Runnable{
 			repService.save(diagramPO);
 			
 			logger.info("数据转换入库完成:{},{}",diagramPO.getParamCode(),diagramPO.getDisps().size());
+			
+			//功图诊断及量液
+			if("diagram_load".equalsIgnoreCase(diagramPO.getParamCode())) {
+				DiagramDiagnoseFacade.diagnose(diagramPO, paramConfig.getWellId());
+			}
+			
 		}
 		catch(Exception e) {
 			e.printStackTrace();

+ 99 - 0
src/main/java/com/hb/proj/gather/process/diagnose/DiagnoseRepService.java

@@ -0,0 +1,99 @@
+package com.hb.proj.gather.process.diagnose;
+
+import java.util.Date;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import com.hb.proj.gather.model.WellMeasurePO;
+import com.hb.proj.gather.model.WellPumpPO;
+import com.hb.xframework.dao.core.SpringJdbcDAO;
+
+/**
+ * 功图诊断及量液相关持久化
+ * @author cwen
+ *
+ */
+
+@Service
+public class DiagnoseRepService {
+
+	@Autowired
+	private SpringJdbcDAO  dao;
+	
+	@Value("${diagnose.base.path}")
+	private String diagnoseBasePath;
+	
+	public String getDiagnoseBasePath() {
+		return diagnoseBasePath;
+	}
+	
+	/**
+	 * 加载所有标准功图
+	 * @return
+	 */
+	public List<DiagramDiagnoseStandardVO> loadAllDiagnoseStandard(){
+		String sql="""
+				select d.*,access_path
+				from tzl_diagram_diagnose_standard d
+				left join tsys_file_info f on d.file_id=f.file_id
+				where d.del_if=false
+				""";
+		return dao.queryForList(sql, DiagramDiagnoseStandardVO.class);
+	}
+	
+	/**
+	 * 获得指定时间前最新的测量数据
+	 * @param wellId
+	 * @param beforeTime
+	 * @return
+	 */
+	public WellMeasurePO  getLastMeasureBefore(String wellId,Date beforeTime) {
+		String sql="""
+				select * from tzl_well_measure 
+				where del_if=false and well_id=? and create_time<?
+				order by create_time desc limit 1
+				""";
+		return dao.queryForPojo(sql, WellMeasurePO.class, wellId,beforeTime);
+	}
+	
+	/**
+	 * 获得最新的油泵参数(指定时间之前)
+	 * @param wellId
+	 * @return
+	 */
+	public WellPumpPO getLastPumpParam(String wellId,Date beforeTime) {
+		String sql="""
+				select * from tzl_pump 
+				where del_if=false and well_id=?
+				and create_time<?
+				order by create_time desc limit 1
+				""";
+		return dao.queryForPojo(sql, WellPumpPO.class,wellId,beforeTime);
+	}
+	
+	
+	public Long getDiagramInsertId(String wellParam,Date gatherTime) {
+		String sql="""
+				select data_id from tzl_gather_data_multi
+				where well_param=? and gather_time=?
+				""";
+		
+		return dao.queryForObject(sql, Long.class, wellParam,gatherTime);
+	}
+	
+	/**
+	 * 保存功图诊断结果
+	 * @param log
+	 */
+	public void saveDiagnoseLog(DiagramDiagnoseLogPO  log) {
+		//先删除旧的诊断记录
+		dao.exeUpdate("delete from tzl_diagram_diagnose_log where diagram_id=?", log.getDiagramId());
+		
+		log.setCreateTime(new Date());
+		log.setModifyTime(new Date());
+		dao.insert(log, "tzl_diagram_diagnose_log", "diagnose_log_id");
+	}
+}

+ 60 - 0
src/main/java/com/hb/proj/gather/process/diagnose/DiagramChartBuilder.java

@@ -0,0 +1,60 @@
+package com.hb.proj.gather.process.diagnose;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import com.hb.proj.gather.model.DiagramPO;
+
+
+public class DiagramChartBuilder {
+	
+	public static void writeDiagramAsJPEG(OutputStream out,DiagramPO diagram) {
+		writeDiagramAsJPEG(out,diagram,360,180);
+	}
+	
+	public static byte[] writeDiagramToByte(DiagramPO diagram,int width, int height) throws IOException{
+		ByteArrayOutputStream  bos=new ByteArrayOutputStream();
+        writeDiagramAsJPEG(bos,diagram,width,height);
+		return bos.toByteArray();
+	}
+
+	public static void writeDiagramAsJPEG(OutputStream out, DiagramPO diagram,int width, int height){
+		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
+		Graphics2D g2 = image.createGraphics();
+		DiagramRender render=new DiagramRender(width,height);
+		render.draw(g2,convert(diagram.getDisps()),convert(diagram.getOths()),convert(diagram.getStroke()),convert(diagram.getGlbMax()),convert(diagram.getGlbMin()));
+        if(g2!=null){
+			g2.dispose();
+		}
+        try {
+			ImageIO.write(image, "png", out);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+       
+	}
+	
+	private static double convert(Float val) {
+		return val!=null?val.doubleValue():0;
+	}
+	
+	private static List<Double> convert(List<Float> vals) {
+		if(vals==null || vals.size()==0) {
+			return null;
+		}
+		List<Double> rtn=new ArrayList<>(vals.size());
+		for(Float f : vals) {
+			rtn.add(f.doubleValue());
+		}
+		return rtn;
+	}
+	
+	
+}

+ 130 - 0
src/main/java/com/hb/proj/gather/process/diagnose/DiagramDiagnoseFacade.java

@@ -0,0 +1,130 @@
+package com.hb.proj.gather.process.diagnose;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+
+import com.hb.proj.gather.model.DiagramPO;
+import com.hb.proj.gather.model.WellMeasurePO;
+import com.hb.proj.gather.model.WellPumpPO;
+import com.hb.xframework.util.ApplicationContextUtils;
+
+/**
+ * 功图诊断入口类
+ * @author cwen
+ *
+ */
+
+public class DiagramDiagnoseFacade {
+
+	private final static  Logger logger = LoggerFactory.getLogger(DiagramDiagnoseFacade.class);
+	
+	private static List<DiagramDiagnoseStandardVO> standards=null;
+	
+	private static DiagnoseRepService service=null;
+	
+	static {
+		logger.info("加载标准功图...");
+		service=ApplicationContextUtils.getBean("diagnoseRepService", DiagnoseRepService.class);
+		standards=service.loadAllDiagnoseStandard();
+	}
+	
+	public static void diagnose(DiagramPO diagramData,String wellId) {
+		long s=System.currentTimeMillis();
+		logger.info("开始功图诊断{}  {}",diagramData.getDevSerial(),service.getDiagnoseBasePath());
+		String basePath=service.getDiagnoseBasePath();
+		String srcChart=createDiagramChart(basePath,diagramData);
+		
+		double maxVal=0,temp=0;
+		DiagramDiagnoseStandardVO similarVO=standards.get(0);
+		for(DiagramDiagnoseStandardVO std : standards) {
+			temp=SSIMExample.calcSimilary(basePath+std.getAccessPath(),srcChart);
+			//logger.info("与模板功图【{}】,相似度{}",std.getConclusion(), temp);
+			if(temp > maxVal) {
+				similarVO=std;
+				maxVal=temp;
+			}
+			
+		}
+		
+		//删除被诊断的临时功图图片
+		FileUtils.deleteQuietly(FileUtils.getFile(srcChart)); 
+		
+		//转存诊断结果
+		Long diagramId=service.getDiagramInsertId(diagramData.getWellParam(), diagramData.getGatherTime());
+		DiagramDiagnoseLogPO diagnoseLog=new DiagramDiagnoseLogPO();
+		diagnoseLog.setWellId(wellId);
+		diagnoseLog.setDiagramId(diagramId);
+		BeanUtils.copyProperties(similarVO, diagnoseLog);
+		diagnoseLog.setCreateBy("gather");
+		
+		//功图量液计算
+		diagramMeasureCalc(diagnoseLog,diagramData);
+		
+		service.saveDiagnoseLog(diagnoseLog);
+		
+		logger.info("完成功图诊断,cost:{}",System.currentTimeMillis()-s);
+	}
+	
+	
+	
+	
+	/**
+	 * 根据功图数据生成图片(存于服务器)
+	 * @param basePath
+	 * @param diagramData
+	 * @param sortPath
+	 * @return
+	 */
+	public static String createDiagramChart(String basePath,DiagramPO diagramData) {
+		String fullName=basePath+File.separator+"upload"+File.separator+"temp"+File.separator+UUID.randomUUID()+".png";
+		try(FileOutputStream fout=FileUtils.openOutputStream(new File(fullName))) {
+			
+			DiagramChartBuilder.writeDiagramAsJPEG(fout, diagramData);
+			fout.flush();
+			fout.close();
+			
+		}
+		catch(IOException e) {
+			logger.error(e.getMessage());
+			e.printStackTrace();
+		}
+		
+		return fullName;
+	}
+	
+	/**
+	 * 功图量液计算
+	 * @param diagnoseLog
+	 * @param diagramData
+	 * @param wellId
+	 * @param diagramTime
+	 */
+	public static void diagramMeasureCalc(DiagramDiagnoseLogPO diagnoseLog,DiagramPO diagramData) {
+		String wellId=diagnoseLog.getWellId();
+		Date diagramTime=diagramData.getGatherTime();
+		WellPumpPO pump=null;
+		WellMeasurePO measure=service.getLastMeasureBefore(wellId,diagramTime);
+		if(measure!=null) {
+			pump=service.getLastPumpParam(wellId,diagramTime);
+		}
+		if(measure==null || pump==null) {
+			diagnoseLog.setNote("缺少井生产基础数据或泵基础数据,无法进行功图量液相关计算");
+		}
+		else {
+			boolean rst=DiagramMeasureCalculator.calcComPumpEff(diagnoseLog, measure);
+			if(rst) {
+				DiagramMeasureCalculator.calcYield(diagnoseLog, diagramData.getStroke().doubleValue(),diagramData.getFreq().doubleValue(), measure, pump);
+			}
+			
+		}
+	}
+}

+ 139 - 0
src/main/java/com/hb/proj/gather/process/diagnose/DiagramDiagnoseLogPO.java

@@ -0,0 +1,139 @@
+package com.hb.proj.gather.process.diagnose;
+
+import java.util.Date;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+public class DiagramDiagnoseLogPO {
+
+	private Integer diagnoseLogId;
+	
+	/**
+	 * 井号
+	 */
+	@NotBlank(message="井号不能为空")
+	private String wellId;
+	
+	/**
+	 * 诊断所用的标准记录号(为null表示手动诊断填写的数据)
+	 */
+	private String diagnoseId;
+	
+	/**
+	 * 被诊断功图记录号
+	 */
+	@NotNull(message="功图记录号不能为空")
+	private Long diagramId;
+	
+	/**
+	 * 弹性系数
+	 */
+	private Double elasticCoe;
+	
+	
+	/**
+	 * 充满系数(%)
+	 */
+	private Double fillCoe;
+	
+	/**
+	 * 漏失系数
+	 */
+	private Double leakCoe;
+	
+	/**
+	 * 气体影响程度  如:严重、一般
+	 */
+	private Double gasEffect;
+	
+	/**
+	 * 排出系数
+	 */
+	private Double dischargeCoe;
+	
+	
+	/**
+	 * 排出漏失系数
+	 */
+	private Double dischargeLeakCoe;
+	
+	/**
+	 * 吸入漏失系数
+	 */
+	private Double  inhaleLeakCoe;
+	
+	/**
+	 * 油管漏失系数
+	 */
+	private Double  pipeLeakCoe;
+	
+	/**
+	 * 出砂程度  如:严重、一般
+	 */
+	private String sandDegree;
+	
+	/**
+	 * 结蜡程度
+	 */
+	private String waxDegree;
+	
+	/**
+	 * 活塞脱出程度
+	 */
+	private String pistonSlipDegree;
+	
+	/**
+	 * 下行碰泵程度
+	 */
+	private String  pumpDownBump;
+	
+	
+	/**
+	 * 诊断结论
+	 */
+	@NotBlank(message="诊断结论不能为空")
+	private String  conclusion;
+	
+	/**
+	 * 地表压缩系数:tzl_well_measure最近数据快照
+	 */
+	private Double surfaceCompCoe;
+	
+	/**
+	 * 综合泵效=弹性系数╳充满度╳排出系数╳漏失系数╳地表压缩系数
+	 * 除地表压缩系数,其它参数取诊断结果中对应数据
+	 */
+	private Double comPumpEff;
+	
+	/**
+	 * 功图液量=(1440╳柱塞面积╳冲数╳光杆冲程/1000000)╳综合泵效
+	 * 柱塞面积:tzl_pump最近数据快照
+	 * 冲数、光杆冲程、综合泵效:自身数据
+	 */
+	private Double gtLiquidYield;
+	
+	/**
+	 * 功图油量=功图液量*(1-含水率)
+	 * 含水率:tzl_well_measure最近数据快照
+	 */
+	private Double gtOilYield;
+	
+	/**
+	 * 功图水量=功图液量*含水率
+	 * 含水率:tzl_well_measure最近数据快照
+	 */
+	private Double gtWaterYield;
+	
+	private Date createTime;
+	
+	private Date modifyTime;
+	
+	private String createBy;
+	
+	private String modifyBy;
+	
+	private String note;
+}

+ 101 - 0
src/main/java/com/hb/proj/gather/process/diagnose/DiagramDiagnoseStandardVO.java

@@ -0,0 +1,101 @@
+package com.hb.proj.gather.process.diagnose;
+
+import java.util.Date;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class DiagramDiagnoseStandardVO {
+
+	private String accessPath;
+	
+	private String diagnoseId;
+	
+	/**
+	 * 标准图形文件id
+	 */
+	private String fileId;
+	
+	/**
+	 * 充满系数(%)
+	 */
+	private Double fillCoe;
+	
+	/**
+	 * 漏失系数
+	 */
+	private Double leakCoe;
+	
+	/**
+	 * 气体影响程度  如:严重、一般
+	 */
+	private Double gasEffect;
+	
+	
+	/**
+	 * 排出系数
+	 */
+	private Double dischargeCoe;
+	
+	/**
+	 * 弹性系数
+	 */
+	private Double elasticCoe;
+	
+	/**
+	 * 排出漏失系数
+	 */
+	private Double dischargeLeakCoe;
+	
+	/**
+	 * 吸入漏失系数
+	 */
+	private Double  inhaleLeakCoe;
+	
+	/**
+	 * 油管漏失系数
+	 */
+	private Double  pipeLeakCoe;
+	
+	/**
+	 * 出砂程度  如:严重、一般
+	 */
+	private String sandDegree;
+	
+	/**
+	 * 结蜡程度
+	 */
+	private String waxDegree;
+	
+	/**
+	 * 活塞脱出程度
+	 */
+	private String pistonSlipDegree;
+	
+	/**
+	 * 下行碰泵程度
+	 */
+	private String  pumpDownBump;
+	
+	/**
+	 * 诊断结论
+	 */
+	@NotBlank(message="诊断结论不能为空")
+	private String  conclusion;
+	
+	/**
+	 * 建议措施
+	 */
+	private String advise;
+	
+	private Date createTime;
+	
+	private Date modifyTime;
+	
+	private String createBy;
+	
+	private String modifyBy;
+	
+	private Boolean delIf;
+}

+ 69 - 0
src/main/java/com/hb/proj/gather/process/diagnose/DiagramMeasureCalculator.java

@@ -0,0 +1,69 @@
+package com.hb.proj.gather.process.diagnose;
+
+import com.hb.proj.gather.model.WellMeasurePO;
+import com.hb.proj.gather.model.WellPumpPO;
+
+/**
+ * 功图量液相关计算
+ * @author cwen
+ *
+ */
+public class DiagramMeasureCalculator {
+
+	/**
+	 * 综合泵效=弹性系数╳充满度╳排出系数╳漏失系数╳地表压缩系数
+	 * 地表压缩系数:tzl_well_measure最近数据快照
+	 * 除地表压缩系数,其它参数取诊断结果中对应数据
+	 */
+	public static boolean calcComPumpEff(DiagramDiagnoseLogPO diagnose,WellMeasurePO measure) {
+		if(diagnose.getElasticCoe()==null || diagnose.getFillCoe()==null || diagnose.getDischargeCoe()==null || diagnose.getLeakCoe()==null) {
+			diagnose.setNote("诊断结果数据不全,无法进行综合泵效计算");
+			return false;
+		}
+		if(measure==null || measure.getSurfaceCompCoe()==null) {
+			diagnose.setNote("缺少地表压缩系数,无法进行综合泵效计算");
+			return false;
+		}
+		
+		//除以100因为充满度的值为百分数
+		double rst=diagnose.getElasticCoe()*diagnose.getFillCoe()*diagnose.getDischargeCoe()*diagnose.getLeakCoe()*measure.getSurfaceCompCoe()/100.0;
+		diagnose.setComPumpEff(rst);
+		diagnose.setSurfaceCompCoe(measure.getSurfaceCompCoe());
+		return true;
+	}
+	
+	/**
+	 * 计算功图量液=(1440╳柱塞面积╳冲数╳光杆冲程/1000000)╳综合泵效
+	 * @param diagnose 诊断及中间计算结果
+	 * @param diagramStroke  功图中的冲程
+	 * @param diagramFreq  功图中的冲次
+	 * @param measure 井生产参数
+	 * @param pump 泵相关基础参数
+	 */
+	public static boolean calcYield(DiagramDiagnoseLogPO diagnose,double diagramStroke,double diagramFreq, WellMeasurePO measure,WellPumpPO pump) {
+		String preNote=diagnose!=null&&diagnose.getNote()!=null?diagnose.getNote():"";
+		
+		if(diagnose==null || diagnose.getComPumpEff()==null ) {
+			diagnose.setNote(preNote+" 缺少综合泵效,无法进行功图量液计算");
+			return false;
+		}
+		
+		if(pump==null || pump.getPi()==null || pump.getBasicDiam()==null ) {
+			diagnose.setNote(preNote+" 缺少泵基础数据,无法进行功图量液计算");
+			return false;
+		}
+		
+		double rst=pump.getPi()*Math.pow(pump.getBasicDiam()/2,2)*diagramStroke*1440*diagramFreq/1000000;
+		rst=rst*diagnose.getComPumpEff();
+		
+		diagnose.setGtLiquidYield(rst);
+		
+		//有含水率时,分别计算油量、水量
+		if(measure.getWaterRatio()!=null) {
+			diagnose.setGtWaterYield(rst*measure.getWaterRatio().doubleValue()*0.01);
+			diagnose.setGtOilYield(rst-diagnose.getGtWaterYield().doubleValue());
+		}
+		
+		return true;
+	}
+}

+ 136 - 0
src/main/java/com/hb/proj/gather/process/diagnose/DiagramRender.java

@@ -0,0 +1,136 @@
+package com.hb.proj.gather.process.diagnose;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.geom.Line2D;
+import java.util.List;
+
+/**
+ * 功图绘制,用于功图诊断,作为被诊断对象、标准功图
+ * 只需要闭合曲线,其他不需要绘制
+ * 功图诊断时,最好标准功图和待诊断功图出自同一绘制方法、同大小
+ * @author cwen
+ *
+ */
+public class DiagramRender {
+
+	private Color canvasColor;
+	private int canvasWidth;
+	private int canvasHeight;
+	
+	private int originX ;  //原点x值
+    private int originY ; //原点y值
+	
+    private int xAxisEnd;
+	private int yAxisEnd;
+	
+    
+    private double xMinValue;
+    private double yMinValue;
+    private double xMaxValue;
+    private double yMaxValue;
+    private double xRatio;
+    private double yRatio;
+   
+    
+    public DiagramRender(){
+		this(360,180);
+	}
+    
+    public DiagramRender(int canvasWidth,int canvasHeight){
+    	this.canvasWidth=canvasWidth;
+		this.canvasHeight=canvasHeight;
+		this.canvasColor=new Color(255,255,255);
+		this.originX=10;
+		this.originY=canvasHeight-10;
+		this.xAxisEnd=canvasWidth-10;
+		this.yAxisEnd=10;
+		this.xMaxValue=3;
+		this.xMinValue=0;
+		this.yMaxValue=50;
+		this.yMinValue=0;
+	}
+    
+    public void draw(Graphics2D g2,List<Double> disps,List<Double> oths,double stroke,double maxOth,double minOth) {
+    	this.xMinValue=0;
+    	this.xMaxValue=Math.ceil(stroke);
+    	this.yMinValue=Math.floor(minOth); //定格画,避免井的个体特性影响相似性判断,相当于各类图缩放在一个区域内
+    	this.yMaxValue=Math.ceil(maxOth);
+    	
+    	this.xRatio=(this.xAxisEnd-this.originX)/(this.xMaxValue-this.xMinValue);
+    	this.yRatio=(this.originY-this.yAxisEnd)/(this.yMaxValue-this.yMinValue);
+    	
+    	this.drawCanvas(g2);
+    	//this.drawCoordinate(g2);
+    	this.drawCurve(g2, disps, oths);
+    }
+    
+    
+    
+    /**
+     * 绘制画布
+     * @param g2
+     */
+	public void drawCanvas(Graphics2D g2){
+		g2.setColor(this.canvasColor);
+		g2.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
+	}
+	
+	/**
+	 * 简易坐标轴
+	 * @param g2
+	 */
+	public void drawCoordinate(Graphics2D g2) {
+		g2.setStroke(new BasicStroke(1));
+		g2.setColor(new Color(212,212,212));
+		g2.drawLine(this.originX, this.originY, this.originX, this.yAxisEnd);     //y 轴
+		g2.drawLine(this.xAxisEnd, this.originY, this.xAxisEnd, this.yAxisEnd);     //y border
+		g2.drawLine(this.originX, this.yAxisEnd, this.xAxisEnd, this.yAxisEnd);     //x border
+		g2.drawLine(this.originX, this.originY,this.xAxisEnd,this.originY);     //x bottom-border
+		
+		int yGirdCount=5;
+		double yGridDivide=(this.originY-this.yAxisEnd)/yGirdCount;
+		double tempy=0;
+		for(int i=1;i<yGirdCount;i++) {
+			tempy=this.originY-i*yGridDivide;
+			g2.draw(new Line2D.Double(this.originX, tempy, this.xAxisEnd,tempy));
+		}
+	}
+	
+	/**
+	 * 绘制曲线
+	 * @param g2
+	 * @param disps
+	 * @param oths
+	 */
+	public void drawCurve(Graphics2D g2,List<Double> disps,List<Double> oths) {
+		g2.setStroke(new BasicStroke(1));
+		g2.setPaint(new Color(0,0,0));
+		double ptx=getX(disps.get(0)),pty=getY(oths.get(0));
+		double nptx=0,npty=0;
+		for(int i=1,len=disps.size(),len2=oths.size();i<len && i<len2;i++) {
+			nptx=getX(disps.get(i));
+			npty=getY(oths.get(i));
+			g2.draw(new Line2D.Double(ptx, pty, nptx,npty));
+			
+			ptx=nptx;
+			pty=npty;
+		}
+		
+		nptx=getX(disps.get(0));
+		npty=getY(oths.get(0));
+		g2.draw(new Line2D.Double(ptx, pty, nptx,npty));
+	}
+	
+	
+	private double getX(double val) { 
+		return this.originX+(val-this.xMinValue)*this.xRatio;
+	}
+
+	private double getY(double val) { 
+		return this.originY-(val-this.yMinValue)*this.yRatio;
+	}
+	
+	
+}

+ 151 - 0
src/main/java/com/hb/proj/gather/process/diagnose/SSIMExample.java

@@ -0,0 +1,151 @@
+package com.hb.proj.gather.process.diagnose;
+
+import org.opencv.core.Core;
+import org.opencv.core.CvType;
+import org.opencv.core.Mat;
+import org.opencv.core.Scalar;
+import org.opencv.core.Size;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+
+public class SSIMExample {
+
+	static {
+        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
+    }
+
+    private static final Scalar C1 = new Scalar(6.5025);
+    private static final Scalar C2 = new Scalar(58.5225);
+    
+  
+    public static void main(String[] args) {
+    	
+    	String imagePath1="C:\\Users\\cwen\\Desktop\\p2.png";
+    	String imagePath2="C:\\Users\\cwen\\Desktop\\p1.png";
+    
+        Mat img1 = Imgcodecs.imread(imagePath1, Imgcodecs.IMREAD_GRAYSCALE);
+        Mat img2 = Imgcodecs.imread(imagePath2, Imgcodecs.IMREAD_GRAYSCALE);
+
+        double ssimValue = calculateSSIM2(img1, img2);
+        System.out.println("SSIM: " + ssimValue);
+    }
+    
+    public static double calcSimilary(String chartFile1,String chartFile2) {
+    	Mat image1 = Imgcodecs.imread(chartFile1,Imgcodecs.IMREAD_GRAYSCALE);  
+	    Mat image2 = Imgcodecs.imread(chartFile2,Imgcodecs.IMREAD_GRAYSCALE); 
+	    //System.out.println(image1.size()+":"+image2.size());
+	    
+	    Imgproc.resize(image1, image1, image2.size());
+	    return calculateSSIM(image1, image2);
+    }
+
+    private static double calculateSSIM(Mat image1, Mat image2) {
+    	Mat validImage1 = new Mat(), validImage2 = new Mat();
+    	image1.convertTo(validImage1, CvType.CV_32F); //数据类型转换为 float,防止后续计算出现错误
+        image2.convertTo(validImage2, CvType.CV_32F);
+        
+        Mat image1_1 = validImage1.mul(validImage1); //图像乘积
+        Mat image2_2 = validImage2.mul(validImage2);
+        Mat image1_2 = validImage1.mul(validImage2);
+        
+        Mat gausBlur1= new Mat(), gausBlur2= new Mat(), gausBlur12= new Mat();
+        Imgproc.GaussianBlur(validImage1, gausBlur1, new Size(11, 11), 1.5); //高斯卷积核计算图像均值
+        Imgproc.GaussianBlur(validImage2, gausBlur2, new Size(11, 11), 1.5);
+        Imgproc.GaussianBlur(image1_2, gausBlur12, new Size(11, 11), 1.5);
+   
+        //Mat imageAvgProduct = gausBlur1.mul(gausBlur2); //均值乘积
+        Mat u1Squre = gausBlur1.mul(gausBlur1); //各自均值的平方
+        Mat u2Squre = gausBlur2.mul(gausBlur2);
+        
+        Mat imageConvariance= new Mat(), imageVariance1= new Mat(), imageVariance2= new Mat();
+        Mat squreAvg1= new Mat(), squreAvg2= new Mat();
+        Imgproc.GaussianBlur(image1_1, squreAvg1, new Size(11, 11), 1.5); //图像平方的均值
+        Imgproc.GaussianBlur(image2_2, squreAvg2, new Size(11, 11), 1.5);
+        
+        //Core.subtract(imageVariance2, squreAvg1, squreAvg2);
+        
+        /*
+        imageConvariance = gausBlur12 - gausBlur1.mul(gausBlur2);// 计算协方差
+        imageVariance1 = squreAvg1 - gausBlur1.mul(gausBlur1); //计算方差
+        imageVariance2 = squreAvg2 - gausBlur2.mul(gausBlur2); 
+  		*/
+        
+        Core.subtract(gausBlur12, gausBlur1.mul(gausBlur2), imageConvariance);
+        Core.subtract(squreAvg1, gausBlur1.mul(gausBlur1), imageVariance1);
+        Core.subtract(squreAvg2, gausBlur2.mul(gausBlur2), imageVariance2);
+        
+        Mat rst=new Mat();
+        Core.multiply(gausBlur1.mul(gausBlur2),new Scalar(2),rst);
+        Core.add(rst, C1, rst);
+        
+        Mat rst2=new Mat();
+        Core.multiply(imageConvariance,new Scalar(2),rst2);
+        Core.add(rst2, C2, rst2);
+        
+        rst.mul(rst2);
+        
+        Mat rst3=new Mat();
+        Core.add(u1Squre, u2Squre, rst3);
+        Core.add(rst3, C1,rst3);
+        
+        Mat rst4=new Mat();
+        Core.add(imageVariance1,imageVariance2,rst4);
+        Core.add(rst4,C2,rst4);
+        rst3.mul(rst4);
+        
+        
+        Mat ssim=new Mat();
+        Core.divide(rst, rst3, ssim);
+        
+        return Core.mean(ssim).val[0];
+        
+        //member  ((2 * gausBlur1 .mul(gausBlur2) + C1).mul(2 * imageConvariance + C2));
+        //auto denominator = ((u1Squre + u2Squre + C1).mul(imageVariance1 + imageVariance2 + C2));
+    }
+    
+    private static double calculateSSIM2(Mat image1, Mat image2) {
+    	Mat validImage1 = new Mat(), validImage2 = new Mat();
+    	image1.convertTo(validImage1, CvType.CV_32F); //数据类型转换为 float,防止后续计算出现错误
+        image2.convertTo(validImage2, CvType.CV_32F);
+        
+        Scalar C3=new Scalar(C2.val[0]/2);
+        
+        Mat image1_1 = validImage1.mul(validImage1); //图像乘积
+        Mat image2_2 = validImage2.mul(validImage2);
+        Mat image1_2 = validImage1.mul(validImage2);
+        
+        Mat gausBlur1= new Mat(), gausBlur2= new Mat(), gausBlur12= new Mat();
+        Imgproc.GaussianBlur(validImage1, gausBlur1, new Size(11, 11), 1.5); //高斯卷积核计算图像均值
+        Imgproc.GaussianBlur(validImage2, gausBlur2, new Size(11, 11), 1.5);
+        Imgproc.GaussianBlur(image1_2, gausBlur12, new Size(11, 11), 1.5);
+        
+        //Mat u1Squre = gausBlur1.mul(gausBlur1); //各自均值的平方
+        //Mat u2Squre = gausBlur2.mul(gausBlur2);
+        Mat squreAvg1= new Mat(), squreAvg2= new Mat();
+        Imgproc.GaussianBlur(image1_1, squreAvg1, new Size(11, 11), 1.5); //图像平方的均值
+        Imgproc.GaussianBlur(image2_2, squreAvg2, new Size(11, 11), 1.5);
+        
+        Mat imageConvariance= new Mat(),imageVariance1= new Mat(), imageVariance2= new Mat();
+        Core.subtract(gausBlur12, gausBlur1.mul(gausBlur2), imageConvariance);
+        Core.subtract(squreAvg1, gausBlur1.mul(gausBlur1), imageVariance1);
+        Core.subtract(squreAvg2, gausBlur2.mul(gausBlur2), imageVariance2);
+        
+        Mat rst=new Mat();
+        Core.add(imageConvariance, C3, rst);
+        
+        Mat sqrtVar1=new Mat();
+        Core.sqrt(imageVariance1, sqrtVar1);
+        
+        Mat sqrtVar2=new Mat();
+        Core.sqrt(imageVariance2, sqrtVar2);
+        
+        Mat rst2=new Mat();
+        sqrtVar1.mul(sqrtVar2);
+        Core.add(sqrtVar1, C3, rst2);
+        
+        Mat ssim=new Mat();
+        Core.divide(rst, rst2, ssim);
+        
+        return Core.mean(ssim).val[0];
+    }
+}

+ 2 - 1
src/main/java/com/hb/proj/gather/rep/WellConfigService.java

@@ -45,6 +45,7 @@ public class WellConfigService {
 	
 	/**
 	 * 加载指定井的参数配置
+	 * 1口井挂载多个设备,必须限定为采集设备 device_type='gather' 
 	 */
 	public Map<String,WellParamVO>  loadWellParams(String wellId){
 		
@@ -52,7 +53,7 @@ public class WellConfigService {
 		
 		String sql="""
 				select 
-				(select device_code from tzl_gather_device d where d.well_id=wp.well_id and d.del_if=false limit 1) device_code,
+				(select device_code from tzl_gather_device d where d.well_id=wp.well_id and d.del_if=false and device_type='gather' limit 1) device_code,
 				param_id,well_id,param_code,gat_ins_scale,gat_ins_scale2,calibrate_a,calibrate_b,calibrate_c, 
 				calibrate_a2,calibrate_b2,calibrate_c2,display_format,dis_ins_scale
 				from tzl_well_param wp

+ 1 - 1
src/main/java/com/hb/proj/gather/scheduler/GatherScheduler.java

@@ -95,7 +95,7 @@ public class GatherScheduler {
 		timer.schedule(singleTask, 60000, config.getSingle()*1000);
 		
 		//多值2分钟后执行,每config.getDiagram()秒重复执行
-		timer.schedule(diagramTask, 120000, config.getDiagram()*1000); 
+		timer.schedule(diagramTask, 100000, config.getDiagram()*1000); 
 		
 		 //动液面3分钟后执行,每config.getLiquid()秒重复执行
 		timer.schedule(liquidTask, 180000, config.getLiquid()*1000);

+ 2 - 1
src/main/resources/application-dev.properties

@@ -42,4 +42,5 @@ spring.data.redis.host=42.56.120.92
 spring.data.redis.port=9608
 spring.data.redis.password=redis7.0
 
-
+#功图诊断基础目录
+diagnose.base.path=D:/workspace/springboot3/zl-opd-server

+ 4 - 1
src/main/resources/application-pro.properties

@@ -59,4 +59,7 @@ api.filter.exclude=/login
 
 #系统管理员
 sys.admin.account=admin
-sys.admin.pwd=2f459bf76f471ff9753caffd1be02bac
+sys.admin.pwd=2f459bf76f471ff9753caffd1be02bac
+
+#功图诊断基础目录
+diagnose.base.path=