张世豪
4 天以前 6700283f9103a45bc087838ebf3eeeeb9022dd98
新增了割草机长宽和割草安全距离属性
已添加1个文件
已修改4个文件
442 ■■■■■ 文件已修改
device.properties 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
set.properties 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/gecaoji/Device.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/gecaoji/MowerSafetyDistanceCalculator.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/set/Sets.java 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
device.properties
@@ -1,5 +1,5 @@
#Updated base station information
#Thu Dec 18 16:15:01 CST 2025
#Updated device properties
#Fri Dec 19 12:08:39 CST 2025
BupdateTime=-1
GupdateTime=-1
baseStationCardNumber=-1
@@ -11,12 +11,15 @@
differentialAge=-1
heading=-1
mowerBladeHeight=-1
mowerLength=3.00
mowerLightStatus=-1
mowerModel=-1
mowerName=-1
mowerNumber=-1
mowerStartStatus=-1
mowerWidth=2.00
mowingHeight=-1
mowingSafetyDistance=1.86
mowingWidth=-1
pitch=-1
positioningStatus=-1
@@ -27,3 +30,4 @@
realtimeX=-1
realtimeY=-1
satelliteCount=-1
selfCheckStatus=-1
set.properties
@@ -1,5 +1,5 @@
#Mower Configuration Properties - Updated
#Fri Dec 19 11:48:07 CST 2025
#Current work land selection updated
#Fri Dec 19 12:08:28 CST 2025
appVersion=-1
boundaryLengthVisible=false
currentWorkLandNumber=LAND1
src/gecaoji/Device.java
@@ -83,6 +83,12 @@
    // å‰²è‰æœºç¯å¼€å…³çŠ¶æ€ï¼š1开启,0关闭,-1未知
    private String mowerBladeHeight = "-1";
    // å‰²è‰æœºåˆ€ç›˜é«˜åº¦ï¼š-1未知
    private String mowerWidth;
    // å‰²è‰æœºå®½åº¦ï¼Œå•位米
    private String mowerLength;
    // å‰²è‰æœºé•¿åº¦ï¼Œå•位米
    private String mowingSafetyDistance;
    // å‰²è‰å®‰å…¨è·ç¦»ï¼Œå•位米
    private static final double METERS_PER_DEGREE_LAT = 111320.0d;
    
@@ -155,6 +161,9 @@
        if (mowerStartStatus != null) properties.setProperty("mowerStartStatus", mowerStartStatus);
        if (mowerLightStatus != null) properties.setProperty("mowerLightStatus", mowerLightStatus);
        if (mowerBladeHeight != null) properties.setProperty("mowerBladeHeight", mowerBladeHeight);
        if (mowerWidth != null) properties.setProperty("mowerWidth", mowerWidth);
        if (mowerLength != null) properties.setProperty("mowerLength", mowerLength);
        if (mowingSafetyDistance != null) properties.setProperty("mowingSafetyDistance", mowingSafetyDistance);
        
        // ä¿å­˜åˆ°æ–‡ä»¶
        try (FileOutputStream output = new FileOutputStream("device.properties");
@@ -198,6 +207,9 @@
        target.mowerStartStatus = properties.getProperty("mowerStartStatus", "-1");
        target.mowerLightStatus = properties.getProperty("mowerLightStatus", "-1");
        target.mowerBladeHeight = properties.getProperty("mowerBladeHeight", "-1");
        target.mowerWidth = properties.getProperty("mowerWidth", "-1");
        target.mowerLength = properties.getProperty("mowerLength", "-1");
        target.mowingSafetyDistance = properties.getProperty("mowingSafetyDistance", "-1");
    }
    private void applyDefaults(Device target) {
@@ -233,6 +245,9 @@
        target.mowerStartStatus = "-1";
        target.mowerLightStatus = "-1";
        target.mowerBladeHeight = "-1";
        target.mowerWidth = "-1";
        target.mowerLength = "-1";
        target.mowingSafetyDistance = "-1";
    }
    public static synchronized Device initializeActiveDevice(String mowerId) { // æ ¹æ®è®¾å¤‡ID初始化活跃设备
@@ -343,6 +358,15 @@
            case "mowerBladeHeight":
                this.mowerBladeHeight = value;
                return true;
            case "mowerWidth":
                this.mowerWidth = value;
                return true;
            case "mowerLength":
                this.mowerLength = value;
                return true;
            case "mowingSafetyDistance":
                this.mowingSafetyDistance = value;
                return true;
            default:
                System.err.println("未知字段: " + fieldName);
                return false;
@@ -835,6 +859,30 @@
        this.mowerBladeHeight = mowerBladeHeight;
    }
    public String getMowerWidth() { // èŽ·å–å‰²è‰æœºå®½åº¦
        return mowerWidth;
    }
    public void setMowerWidth(String mowerWidth) { // è®¾ç½®å‰²è‰æœºå®½åº¦
        this.mowerWidth = mowerWidth;
    }
    public String getMowerLength() { // èŽ·å–å‰²è‰æœºé•¿åº¦
        return mowerLength;
    }
    public void setMowerLength(String mowerLength) { // è®¾ç½®å‰²è‰æœºé•¿åº¦
        this.mowerLength = mowerLength;
    }
    public String getMowingSafetyDistance() { // èŽ·å–å‰²è‰å®‰å…¨è·ç¦»
        return mowingSafetyDistance;
    }
    public void setMowingSafetyDistance(String mowingSafetyDistance) { // è®¾ç½®å‰²è‰å®‰å…¨è·ç¦»
        this.mowingSafetyDistance = mowingSafetyDistance;
    }
    @Override
    public String toString() { // è¾“出对象信息
        return "Device{" +
@@ -866,6 +914,9 @@
                ", mowerStartStatus='" + mowerStartStatus + '\'' +
                ", mowerLightStatus='" + mowerLightStatus + '\'' +
                ", mowerBladeHeight='" + mowerBladeHeight + '\'' +
                ", mowerWidth='" + mowerWidth + '\'' +
                ", mowerLength='" + mowerLength + '\'' +
                ", mowingSafetyDistance='" + mowingSafetyDistance + '\'' +
                '}';
    }
}
src/gecaoji/MowerSafetyDistanceCalculator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
package gecaoji;
import java.text.DecimalFormat;
/**
 * å‰²è‰æœºå®‰å…¨è·ç¦»è®¡ç®—器
 * æ ¹æ®å‰²è‰æœºå°ºå¯¸å’ŒRTK定位精度计算作业时的安全距离
 */
public class MowerSafetyDistanceCalculator {
    // RTK定位精度(单位:米)
    private static final double RTK_ACCURACY = 0.03;
    /**
     * è®¡ç®—综合安全距离
     * @param length å‰²è‰æœºé•¿åº¦ï¼ˆç±³ï¼‰
     * @param width å‰²è‰æœºå®½åº¦ï¼ˆç±³ï¼‰
     * @return ç»¼åˆå®‰å…¨è·ç¦»ï¼ˆç±³ï¼‰ï¼Œä¿ç•™ä¸¤ä½å°æ•°
     */
    public static double calculateSafetyDistance(double length, double width) {
        // éªŒè¯è¾“入参数
        if (length <= 0 || width <= 0) {
            throw new IllegalArgumentException("割草机长度和宽度必须为正数");
        }
        // è®¡ç®—直线行驶时的侧向安全距离(不含误差补偿)
        double lateralSafety = width / 2;
        // è®¡ç®—原地掉头时的旋转半径
        double diagonal = Math.sqrt(length * length + width * width);
        double turnRadius = diagonal / 2;
        // è®¡ç®—综合安全距离(取最大值 + RTK误差补偿)
        double safetyDistance = Math.max(lateralSafety, turnRadius) + 2 * RTK_ACCURACY;
        // ä¿ç•™ä¸¤ä½å°æ•°
        DecimalFormat df = new DecimalFormat("#.##");
        return Double.parseDouble(df.format(safetyDistance));
    }
    /**
     * è®¡ç®—直线行驶时的最小侧向安全距离
     * @param width å‰²è‰æœºå®½åº¦ï¼ˆç±³ï¼‰
     * @return ä¾§å‘安全距离(米),保留两位小数
     */
    public static double calculateLateralSafetyDistance(double width) {
        if (width <= 0) {
            throw new IllegalArgumentException("割草机宽度必须为正数");
        }
        double lateralSafety = width / 2 + 2 * RTK_ACCURACY;
        DecimalFormat df = new DecimalFormat("#.##");
        return Double.parseDouble(df.format(lateralSafety));
    }
    /**
     * è®¡ç®—原地掉头所需的安全半径
     * @param length å‰²è‰æœºé•¿åº¦ï¼ˆç±³ï¼‰
     * @param width å‰²è‰æœºå®½åº¦ï¼ˆç±³ï¼‰
     * @return æŽ‰å¤´å®‰å…¨åŠå¾„(米),保留两位小数
     */
    public static double calculateTurnSafetyRadius(double length, double width) {
        if (length <= 0 || width <= 0) {
            throw new IllegalArgumentException("割草机长度和宽度必须为正数");
        }
        double diagonal = Math.sqrt(length * length + width * width);
        double turnRadius = diagonal / 2 + 2 * RTK_ACCURACY;
        DecimalFormat df = new DecimalFormat("#.##");
        return Double.parseDouble(df.format(turnRadius));
    }
    /**
     * è®¡ç®—两行之间的推荐间距(用于平行线路径规划)
     * @param cuttingWidth å‰²è‰å®½åº¦ï¼ˆç±³ï¼‰
     * @return è¡Œé—´è·ï¼ˆç±³ï¼‰ï¼Œä¿ç•™ä¸¤ä½å°æ•°
     */
    public static double calculateRowSpacing(double cuttingWidth) {
        if (cuttingWidth <= 0) {
            throw new IllegalArgumentException("割草宽度必须为正数");
        }
        // è€ƒè™‘双向误差叠加
        double rowSpacing = cuttingWidth - 2 * RTK_ACCURACY;
        // ç¡®ä¿è¡Œé—´è·ä¸ºæ­£æ•°
        if (rowSpacing <= 0) {
            System.out.println("警告:割草宽度过小,无法保证全覆盖");
            rowSpacing = cuttingWidth; // è¿”回原始值
        }
        DecimalFormat df = new DecimalFormat("#.##");
        return Double.parseDouble(df.format(rowSpacing));
    }
}
src/set/Sets.java
@@ -1,6 +1,8 @@
package set;
import baseStation.BaseStation;
import gecaoji.Device;
import gecaoji.MowerSafetyDistanceCalculator;
import zhuye.MapRenderer;
import zhuye.Shouye;
@@ -32,6 +34,8 @@
    
    // è®¾ç½®é¡¹ç»„ä»¶
    private JLabel mowerIdLabel;
    private JLabel mowerSizeLabel;
    private JLabel mowingSafetyDistanceLabel;
    private JLabel baseStationIdLabel;
    private JLabel handheldMarkerLabel;
    private JLabel simCardNumberLabel;
@@ -43,6 +47,8 @@
    private JLabel measurementModeEnabledLabel;
    
    private JButton mowerIdEditBtn;
    private JButton mowerSizeEditBtn;
    private JButton mowingSafetyDistanceEditBtn;
    private JButton baseStationIdEditBtn;
    private JButton handheldEditBtn;
    private JButton checkUpdateBtn;
@@ -123,6 +129,18 @@
        mowerIdLabel = (JLabel) mowerIdPanel.getClientProperty("valueLabel");
        mowerIdEditBtn = (JButton) mowerIdPanel.getClientProperty("editButton");
        // å‰²è‰æœºé•¿å®½
        JPanel mowerSizePanel = createSettingItemPanel("割草机长宽",
            formatMowerSize(), true);
        mowerSizeLabel = (JLabel) mowerSizePanel.getClientProperty("valueLabel");
        mowerSizeEditBtn = (JButton) mowerSizePanel.getClientProperty("editButton");
        // å‰²è‰å®‰å…¨è·ç¦»
        JPanel mowingSafetyDistancePanel = createSettingItemPanel("割草安全距离",
            formatMowingSafetyDistance(), true);
        mowingSafetyDistanceLabel = (JLabel) mowingSafetyDistancePanel.getClientProperty("valueLabel");
        mowingSafetyDistanceEditBtn = (JButton) mowingSafetyDistancePanel.getClientProperty("editButton");
        JPanel baseStationIdPanel = createSettingItemPanel("差分基准站编号",
            resolveBaseStationId(), true);
        baseStationIdLabel = (JLabel) baseStationIdPanel.getClientProperty("valueLabel");
@@ -167,6 +185,8 @@
        
        // æ·»åŠ è®¾ç½®é¡¹ï¼Œä½¿ç”¨åˆ†å‰²çº¿åˆ†éš”
        addSettingItem(panel, mowerIdPanel, true);
        addSettingItem(panel, mowerSizePanel, true);
        addSettingItem(panel, mowingSafetyDistancePanel, true);
        addSettingItem(panel, baseStationIdPanel, true);
        addSettingItem(panel, handheldPanel, true);
        addSettingItem(panel, simCardPanel, true);
@@ -205,6 +225,67 @@
        return seconds + "秒";
    }
    /**
     * å°†å­—符串值转换为米(兼容旧数据,如果值大于100认为是厘米,需要除以100)
     */
    private double convertToMeters(String value) {
        if (value == null || value.trim().isEmpty() || "-1".equals(value.trim())) {
            return -1;
        }
        try {
            double val = Double.parseDouble(value.trim());
            // å¦‚果值大于100,认为是厘米,需要转换为米
            if (val > 100) {
                return val / 100.0;
            }
            return val;
        } catch (NumberFormatException e) {
            return -1;
        }
    }
    /**
     * æ ¼å¼åŒ–数值为米,保留2位小数
     */
    private String formatMeters(double value) {
        if (value < 0) {
            return "未设置";
        }
        return String.format("%.2f", value) + "m";
    }
    private String formatMowerSize() {
        Device device = Device.getActiveDevice();
        if (device == null) {
            return "未设置";
        }
        String width = device.getMowerWidth();
        String length = device.getMowerLength();
        double widthMeters = convertToMeters(width);
        double lengthMeters = convertToMeters(length);
        if (widthMeters < 0 && lengthMeters < 0) {
            return "未设置";
        }
        String widthStr = widthMeters >= 0 ? formatMeters(widthMeters) : "未设置";
        String lengthStr = lengthMeters >= 0 ? formatMeters(lengthMeters) : "未设置";
        return lengthStr + " Ã— " + widthStr;
    }
    private String formatMowingSafetyDistance() {
        Device device = Device.getActiveDevice();
        if (device == null) {
            return "未设置";
        }
        String distance = device.getMowingSafetyDistance();
        double distanceMeters = convertToMeters(distance);
        if (distanceMeters < 0) {
            return "未设置";
        }
        return formatMeters(distanceMeters);
    }
    
    private JPanel createSettingItemPanel(String title, String value, boolean editable) {
        JPanel panel = new JPanel(new GridBagLayout());
@@ -620,6 +701,11 @@
        // ä»ŽSetsys类加载数据
        setData.initializeFromProperties();
        baseStation.load();
        // ä»Ždevice.properties加载设备数据
        Device device = Device.getActiveDevice();
        if (device != null) {
            device.initFromProperties();
        }
        updateDisplay();
        // åŠ è½½å¹¶åº”ç”¨ä¸Šæ¬¡ä¿å­˜çš„è§†å›¾ä¸­å¿ƒåæ ‡
        loadViewCenterFromProperties();
@@ -671,6 +757,16 @@
            mowerIdLabel.setText(setData.getMowerId() != null ? setData.getMowerId() : "未设置");
        }
        // æ›´æ–°å‰²è‰æœºé•¿å®½æ˜¾ç¤º
        if (mowerSizeLabel != null) {
            mowerSizeLabel.setText(formatMowerSize());
        }
        // æ›´æ–°å‰²è‰å®‰å…¨è·ç¦»æ˜¾ç¤º
        if (mowingSafetyDistanceLabel != null) {
            mowingSafetyDistanceLabel.setText(formatMowingSafetyDistance());
        }
        if (baseStationIdLabel != null) {
            baseStationIdLabel.setText(resolveBaseStationId());
        }
@@ -768,6 +864,16 @@
            mowerIdEditBtn.addActionListener(e -> editMowerId());
        }
        // å‰²è‰æœºé•¿å®½ç¼–辑按钮事件
        if (mowerSizeEditBtn != null) {
            mowerSizeEditBtn.addActionListener(e -> editMowerSize());
        }
        // å‰²è‰å®‰å…¨è·ç¦»ç¼–辑按钮事件
        if (mowingSafetyDistanceEditBtn != null) {
            mowingSafetyDistanceEditBtn.addActionListener(e -> editMowingSafetyDistance());
        }
        if (baseStationIdEditBtn != null) {
            baseStationIdEditBtn.addActionListener(e -> editBaseStationId());
        }
@@ -816,6 +922,181 @@
        }
    }
    private void editMowerSize() {
        Device device = Device.getActiveDevice();
        if (device == null) {
            JOptionPane.showMessageDialog(this, "设备未初始化", "错误", JOptionPane.ERROR_MESSAGE);
            return;
        }
        // èŽ·å–å½“å‰å€¼å¹¶è½¬æ¢ä¸ºç±³ï¼ˆå…¼å®¹æ—§æ•°æ®ï¼‰
        double currentLengthMeters = convertToMeters(device.getMowerLength());
        double currentWidthMeters = convertToMeters(device.getMowerWidth());
        String currentLengthStr = currentLengthMeters >= 0 ? String.format("%.2f", currentLengthMeters) : "";
        String currentWidthStr = currentWidthMeters >= 0 ? String.format("%.2f", currentWidthMeters) : "";
        // åˆ›å»ºè¾“入对话框
        JPanel inputPanel = new JPanel(new GridLayout(2, 2, 5, 5));
        inputPanel.add(new JLabel("长度(m):"));
        JTextField lengthField = new JTextField(currentLengthStr);
        inputPanel.add(lengthField);
        inputPanel.add(new JLabel("宽度(m):"));
        JTextField widthField = new JTextField(currentWidthStr);
        inputPanel.add(widthField);
        int result = JOptionPane.showConfirmDialog(this,
            inputPanel,
            "修改割草机长宽",
            JOptionPane.OK_CANCEL_OPTION,
            JOptionPane.QUESTION_MESSAGE);
        if (result == JOptionPane.OK_OPTION) {
            String newLength = lengthField.getText().trim();
            String newWidth = widthField.getText().trim();
            // éªŒè¯è¾“å…¥
            if (newLength.isEmpty() && newWidth.isEmpty()) {
                JOptionPane.showMessageDialog(this, "长度和宽度不能同时为空", "提示", JOptionPane.WARNING_MESSAGE);
                return;
            }
            double lengthValue = -1;
            double widthValue = -1;
            if (!newLength.isEmpty()) {
                try {
                    lengthValue = Double.parseDouble(newLength);
                    if (lengthValue < 0) {
                        JOptionPane.showMessageDialog(this, "长度必须大于等于0", "提示", JOptionPane.WARNING_MESSAGE);
                        return;
                    }
                } catch (NumberFormatException e) {
                    JOptionPane.showMessageDialog(this, "长度必须是有效的数字", "提示", JOptionPane.WARNING_MESSAGE);
                    return;
                }
            }
            if (!newWidth.isEmpty()) {
                try {
                    widthValue = Double.parseDouble(newWidth);
                    if (widthValue < 0) {
                        JOptionPane.showMessageDialog(this, "宽度必须大于等于0", "提示", JOptionPane.WARNING_MESSAGE);
                        return;
                    }
                } catch (NumberFormatException e) {
                    JOptionPane.showMessageDialog(this, "宽度必须是有效的数字", "提示", JOptionPane.WARNING_MESSAGE);
                    return;
                }
            }
            // æ›´æ–°è®¾å¤‡å±žæ€§ï¼ˆä¿å­˜ä¸ºç±³ï¼Œä¿ç•™2位小数)
            if (lengthValue >= 0) {
                String lengthStr = String.format("%.2f", lengthValue);
                device.setMowerLength(lengthStr);
                device.updateField("mowerLength", lengthStr);
            } else {
                device.setMowerLength("-1");
                device.updateField("mowerLength", "-1");
            }
            if (widthValue >= 0) {
                String widthStr = String.format("%.2f", widthValue);
                device.setMowerWidth(widthStr);
                device.updateField("mowerWidth", widthStr);
            } else {
                device.setMowerWidth("-1");
                device.updateField("mowerWidth", "-1");
            }
            // å¦‚果长度和宽度都有效,自动计算安全距离
            if (lengthValue > 0 && widthValue > 0) {
                try {
                    double safetyDistance = MowerSafetyDistanceCalculator.calculateSafetyDistance(lengthValue, widthValue);
                    String safetyDistanceStr = String.format("%.2f", safetyDistance);
                    device.setMowingSafetyDistance(safetyDistanceStr);
                    device.updateField("mowingSafetyDistance", safetyDistanceStr);
                    // æ›´æ–°å®‰å…¨è·ç¦»æ˜¾ç¤º
                    if (mowingSafetyDistanceLabel != null) {
                        mowingSafetyDistanceLabel.setText(formatMowingSafetyDistance());
                    }
                } catch (IllegalArgumentException e) {
                    // å¦‚果计算失败,不更新安全距离
                    System.err.println("计算安全距离失败: " + e.getMessage());
                }
            }
            // ä¿å­˜åˆ°device.properties
            device.saveToProperties();
            // æ›´æ–°æ˜¾ç¤º
            if (mowerSizeLabel != null) {
                mowerSizeLabel.setText(formatMowerSize());
            }
            JOptionPane.showMessageDialog(this, "割草机长宽更新成功" +
                (lengthValue > 0 && widthValue > 0 ? ",安全距离已自动计算" : ""),
                "成功", JOptionPane.INFORMATION_MESSAGE);
        }
    }
    private void editMowingSafetyDistance() {
        Device device = Device.getActiveDevice();
        if (device == null) {
            JOptionPane.showMessageDialog(this, "设备未初始化", "错误", JOptionPane.ERROR_MESSAGE);
            return;
        }
        // èŽ·å–å½“å‰å€¼å¹¶è½¬æ¢ä¸ºç±³ï¼ˆå…¼å®¹æ—§æ•°æ®ï¼‰
        double currentDistanceMeters = convertToMeters(device.getMowingSafetyDistance());
        String currentValueStr = currentDistanceMeters >= 0 ? String.format("%.2f", currentDistanceMeters) : "";
        String newValue = (String) JOptionPane.showInputDialog(this,
            "请输入割草安全距离(单位:m):",
            "修改割草安全距离",
            JOptionPane.QUESTION_MESSAGE,
            null,
            null,
            currentValueStr);
        if (newValue == null) {
            return; // ç”¨æˆ·å–消
        }
        newValue = newValue.trim();
        if (newValue.isEmpty()) {
            JOptionPane.showMessageDialog(this, "割草安全距离不能为空", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        // éªŒè¯è¾“å…¥
        double distanceValue;
        try {
            distanceValue = Double.parseDouble(newValue);
            if (distanceValue < 0) {
                JOptionPane.showMessageDialog(this, "割草安全距离必须大于等于0", "提示", JOptionPane.WARNING_MESSAGE);
                return;
            }
        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(this, "割草安全距离必须是有效的数字", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        // æ›´æ–°è®¾å¤‡å±žæ€§ï¼ˆä¿å­˜ä¸ºç±³ï¼Œä¿ç•™2位小数)
        String distanceStr = String.format("%.2f", distanceValue);
        device.setMowingSafetyDistance(distanceStr);
        device.updateField("mowingSafetyDistance", distanceStr);
        // ä¿å­˜åˆ°device.properties
        device.saveToProperties();
        // æ›´æ–°æ˜¾ç¤º
        if (mowingSafetyDistanceLabel != null) {
            mowingSafetyDistanceLabel.setText(formatMowingSafetyDistance());
        }
        JOptionPane.showMessageDialog(this, "割草安全距离更新成功", "成功", JOptionPane.INFORMATION_MESSAGE);
    }
    private void editHandheldMarkerId() {
        String currentValue = setData.getHandheldMarkerId() != null ? setData.getHandheldMarkerId() : "";
        String newValue = (String) JOptionPane.showInputDialog(this,