C#呼叫js庫的方法

2023-01-17 12:01:46

前言

用.net6開發一個Winform程式,處理Excel檔案,並把結果匯出Excel檔案。
要用到兩個演演算法,一是turf.js庫的booleanPointInPolygon方法,判斷經緯度座標是否在區域內;二是經緯度糾偏演演算法,因為對方給的區域座標集合有偏移,需要糾偏。
這兩個演演算法,網上找C#的實現,一是不好找;二是找來的不信任,我還要測試以確保沒有問題。我之前做電子地圖使用過turf.js庫和js版本的糾偏演演算法,比較信任,確定沒有問題。
所以我就打算通過C#呼叫js庫的方法,來實現資料處理。

安裝ClearScript

ClearScript是微軟開源的js引擎,支援windows、linux、mac。
NuGet搜尋安裝:
Microsoft.ClearScript.Core
Microsoft.ClearScript.V8
Microsoft.ClearScript.V8.Native.win-x64

引入js檔案


把leaflet.mapCorrection.js、turf.v6.5.0.min.js和自己寫的calc.js放入工程中,右擊屬性設定複製到輸出目錄:如果較新則複製。
calc.js通過呼叫leaflet.mapCorrection.js和turf.v6.5.0.min.js中的方法實現功能,檔案內容如下:

function calc(lng, lat, polygonStr) {
    var point = turf.point([lng, lat]);
    var polygonPoints = JSON.parse(polygonStr);
    var polygon = turf.polygon(polygonPoints);

    var bl = turf.booleanPointInPolygon(point, polygon);
    return bl;
}

function correct(lng, lat) {
    var newPoint = new CoordConvertor().gcj02_To_gps84(lng, lat);
    return newPoint;
}

建立V8ScriptEngine物件

private V8ScriptEngine _engine = new V8ScriptEngine();

通過js引擎載入js檔案

在Form1_Load方法中新增如下程式碼:

_engine.AddHostType("Console", typeof(Console));
string fileName = AppDomain.CurrentDomain.BaseDirectory + "turf.v6.5.0.min.js";
string js;
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    byte[] bArr = new byte[fs.Length];
    await fs.ReadAsync(bArr, 0, bArr.Length);
    js = ASCIIEncoding.UTF8.GetString(bArr);
}
_engine.Execute(js);
fileName = AppDomain.CurrentDomain.BaseDirectory + "calc.js";
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    byte[] bArr = new byte[fs.Length];
    await fs.ReadAsync(bArr, 0, bArr.Length);
    js = ASCIIEncoding.UTF8.GetString(bArr);
}
_engine.Execute(js);
fileName = AppDomain.CurrentDomain.BaseDirectory + "leaflet.mapCorrection.js";
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    byte[] bArr = new byte[fs.Length];
    await fs.ReadAsync(bArr, 0, bArr.Length);
    js = ASCIIEncoding.UTF8.GetString(bArr);
}
_engine.Execute(js);

C#呼叫js方法實現經緯度座標糾偏

double lng = Convert.ToDouble(lnglat[0]);
double lat = Convert.ToDouble(lnglat[1]);

//座標糾偏
dynamic newPoint = _engine.Invoke("correct", new object[] { lng, lat });
lng = newPoint.lng;
lat = newPoint.lat;

C#呼叫js方法判斷經緯度點位是否在多邊形內

//_selectedRegionPoints是多邊形座標點位集合json字串
bool bl = (bool)_engine.Invoke("calc", new object[] { lng, lat, _selectedRegionPoints });

程式開發完成後釋出


釋出後資料夾拷貝到使用者的win10系統中可以直接使用,不需要安裝.net6環境。我自己的很老的win7 sp1虛擬機器器上跑不起來,ClearScriptV8.win-x64.dll無法載入成功,暫不知道為什麼。

Form1.cs完整程式碼如下:

當時程式寫的急,當然,程式還可以優化,不過沒必要,要處理的資料量不大,功能沒問題就行。

using Models;
using Newtonsoft.Json;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.ClearScript.JavaScript;
using Microsoft.ClearScript.V8;
using NPOI.HSSF.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.SS.UserModel;
using System.Reflection;
using System.Windows.Forms;
using NPOI.Util;

namespace 點位
{
    public partial class Form1 : Form
    {
        private Regions _regions;
        private List<CameraInfo> _cameraList = new List<CameraInfo>();
        private V8ScriptEngine _engine = new V8ScriptEngine();
        private string _selectedRegionPoints;

        public Form1()
        {
            InitializeComponent();
        }

        private async void Form1_Load(object sender, EventArgs e)
        {
            //通過js引擎載入js檔案
            _engine.AddHostType("Console", typeof(Console));
            string fileName = AppDomain.CurrentDomain.BaseDirectory + "turf.v6.5.0.min.js";
            string js;
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                byte[] bArr = new byte[fs.Length];
                await fs.ReadAsync(bArr, 0, bArr.Length);
                js = ASCIIEncoding.UTF8.GetString(bArr);
            }
            _engine.Execute(js);
            fileName = AppDomain.CurrentDomain.BaseDirectory + "calc.js";
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                byte[] bArr = new byte[fs.Length];
                await fs.ReadAsync(bArr, 0, bArr.Length);
                js = ASCIIEncoding.UTF8.GetString(bArr);
            }
            _engine.Execute(js);
            fileName = AppDomain.CurrentDomain.BaseDirectory + "leaflet.mapCorrection.js";
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                byte[] bArr = new byte[fs.Length];
                await fs.ReadAsync(bArr, 0, bArr.Length);
                js = ASCIIEncoding.UTF8.GetString(bArr);
            }
            _engine.Execute(js);

            //行政區劃下拉選單初始化
            fileName = AppDomain.CurrentDomain.BaseDirectory + "安徽.json";
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                byte[] bArr = new byte[fs.Length];
                await fs.ReadAsync(bArr, 0, bArr.Length);
                string json = ASCIIEncoding.UTF8.GetString(bArr);
                _regions = JsonConvert.DeserializeObject<Regions>(json);
            }

            List<Records> citys = _regions.RECORDS.ToList().FindAll(a => a.civilcode.Length == 4);
            cbxCity.DataSource = citys;
            cbxCity.DisplayMember = "civilname";
            cbxCity.ValueMember = "civilcode";
        }

        private void button1_Click(object sender, EventArgs e)
        {
            openFileDialog1.Title = "選擇要處理的Excel檔案";
            openFileDialog1.Filter = "Excel檔案(*.xlsx)|*.xlsx";
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                using (FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                {

                }
            }
        }


        private void cbxCity_SelectedIndexChanged(object sender, EventArgs e)
        {
            Records record = cbxCity.SelectedItem as Records;
            List<Records> citys = _regions.RECORDS.ToList().FindAll(a => a.civilcode.Length > 4 && a.civilcode.Substring(0, 4) == record.civilcode);
            citys.Insert(0, new Records() { civilcode = null, civilname = "==請選擇==" });
            cbxCounty.DataSource = citys;
            cbxCounty.DisplayMember = "civilname";
            cbxCounty.ValueMember = "civilcode";
        }

        private void cbxCounty_SelectedIndexChanged(object sender, EventArgs e)
        {
            Records record = cbxCounty.SelectedItem as Records;
            if (record.civilcode == null)
            {
                record = cbxCity.SelectedItem as Records;
            }
            Regex regex = new Regex(@"^POLYGON\((\(.*\),?)*\)$");
            var mc = regex.Matches(record.polygongeo);
            StringBuilder sb = new StringBuilder();
            foreach (Match m in mc)
            {
                string value = m.Groups[1].Value.TrimStart('(').TrimEnd(')');
                string[] lnglatArr = value.Split(',');
                bool first = true;
                if (sb.Length > 0)
                {
                    sb.Append(",");
                }
                sb.Append("[[");
                foreach (string lnglatStr in lnglatArr)
                {
                    string[] lnglat = lnglatStr.Trim().Split(' ');
                    double lng = Convert.ToDouble(lnglat[0]);
                    double lat = Convert.ToDouble(lnglat[1]);

                    //座標糾偏
                    dynamic newPoint = _engine.Invoke("correct", new object[] { lng, lat });
                    lng = newPoint.lng;
                    lat = newPoint.lat;

                    if (first)
                    {
                        first = false;
                        sb.AppendFormat($"[{lng}, {lat}]");
                    }
                    else
                    {
                        sb.AppendFormat($",[{lng}, {lat}]");
                    }
                }
                sb.Append("]]");
            }
            _selectedRegionPoints = sb.ToString();
        }

        private async void openFileDialog1_FileOk(object sender, System.ComponentModel.CancelEventArgs e)
        {
            await Task.Delay(10);

            //讀取Excel
            _cameraList = new List<CameraInfo>();
            using (FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                XSSFWorkbook workbook = new XSSFWorkbook(fs);
                ISheet sheet = workbook.GetSheetAt(0);
                for (int i = 1; i <= sheet.LastRowNum; i++)
                {
                    IRow row = sheet.GetRow(i);
                    CameraInfo cameraInfo = new CameraInfo();
                    cameraInfo.CameraNo = row.GetCell(1).StringCellValue.Trim();
                    cameraInfo.City = row.GetCell(2).StringCellValue.Trim();
                    cameraInfo.County = row.GetCell(3).StringCellValue.Trim();
                    cameraInfo.CameraName = row.GetCell(4).StringCellValue.Trim();
                    cameraInfo.Lng = row.GetCell(5).StringCellValue.Trim();
                    cameraInfo.Lat = row.GetCell(6).StringCellValue.Trim();
                    cameraInfo.CameraFunType = row.GetCell(7).StringCellValue.Trim();
                    cameraInfo.Region = row.GetCell(8).StringCellValue.Trim();
                    cameraInfo.Type = row.GetCell(9).StringCellValue.Trim();
                    cameraInfo.Status = row.GetCell(10).StringCellValue.Trim();
                    cameraInfo.Mac = row.GetCell(11).StringCellValue.Trim();
                    cameraInfo.Ip = row.GetCell(12).StringCellValue.Trim();
                    _cameraList.Add(cameraInfo);
                }
            }

            //過濾資料
            _cameraList = _cameraList.FindAll(cameraInfo =>
            {
                if (!string.IsNullOrWhiteSpace(cameraInfo.Lng) && !string.IsNullOrWhiteSpace(cameraInfo.Lat))
                {
                    double lng = Convert.ToDouble(cameraInfo.Lng);
                    double lat = Convert.ToDouble(cameraInfo.Lat);
                    bool bl = (bool)_engine.Invoke("calc", new object[] { lng, lat, _selectedRegionPoints });
                    if (bl) //區域內
                    {
                        return false;
                    }
                    else //區域外
                    {
                        return true;
                    }
                }
                else
                {
                    return false;
                }
            });

            saveFileDialog1.Title = "選擇處理結果要儲存的位置及檔名";
            saveFileDialog1.Filter = "Excel檔案(*.xlsx)|*.xlsx";
            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                if (File.Exists(saveFileDialog1.FileName))
                {
                    File.Delete(saveFileDialog1.FileName);
                }
                string template = AppDomain.CurrentDomain.BaseDirectory + "點位模板.xlsx";
                XSSFWorkbook workbook;
                using (FileStream fs = new FileStream(template, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    workbook = new XSSFWorkbook(fs);

                    using (FileStream fs2 = new FileStream(saveFileDialog1.FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                    {
                        ISheet sheet = workbook.GetSheetAt(0);
                        sheet.RemoveRow(sheet.GetRow(1));
                        sheet.RemoveRow(sheet.GetRow(2));
                        Dictionary<int, ICellStyle> cellStyles = GetCellStyles(sheet);
                        int i = 1;
                        foreach (CameraInfo cameraInfo in _cameraList)
                        {
                            IRow row = sheet.CreateRow(i);
                            ICell cell1 = row.CreateCell(1, CellType.String);
                            ICell cell2 = row.CreateCell(2, CellType.String);
                            ICell cell3 = row.CreateCell(3, CellType.String);
                            ICell cell4 = row.CreateCell(4, CellType.String);
                            ICell cell5 = row.CreateCell(5, CellType.String);
                            ICell cell6 = row.CreateCell(6, CellType.String);
                            ICell cell7 = row.CreateCell(7, CellType.String);
                            ICell cell8 = row.CreateCell(8, CellType.String);
                            ICell cell9 = row.CreateCell(9, CellType.String);
                            ICell cell10 = row.CreateCell(10, CellType.String);
                            ICell cell11 = row.CreateCell(11, CellType.String);
                            ICell cell12 = row.CreateCell(12, CellType.String);
                            SetCellStyles(row, cellStyles);
                            cell1.SetCellValue(cameraInfo.CameraNo);
                            cell2.SetCellValue(cameraInfo.City);
                            cell3.SetCellValue(cameraInfo.County);
                            cell4.SetCellValue(cameraInfo.CameraName);
                            cell5.SetCellValue(cameraInfo.Lng);
                            cell6.SetCellValue(cameraInfo.Lat);
                            cell7.SetCellValue(cameraInfo.CameraFunType);
                            cell8.SetCellValue(cameraInfo.Region);
                            cell9.SetCellValue(cameraInfo.Type);
                            cell10.SetCellValue(cameraInfo.Status);
                            cell11.SetCellValue(cameraInfo.Mac);
                            cell12.SetCellValue(cameraInfo.Ip);
                            i++;
                        }
                        workbook.Write(fs2);
                    }
                    MessageBox.Show("完成");
                }
            }
        }

        private Dictionary<int, ICellStyle> GetCellStyles(ISheet sheet)
        {
            var styleRow = sheet.GetRow(5);
            Dictionary<int, ICellStyle> result = new Dictionary<int, ICellStyle>();
            for (int i = 1; i <= 12; i++)
            {
                result.Add(i, styleRow.GetCell(i).CellStyle);
            }
            return result;
        }

        private void SetCellStyles(IRow row, Dictionary<int, ICellStyle> styles)
        {
            for (int i = 1; i <= 12; i++)
            {
                row.GetCell(i).CellStyle = styles[i];
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            _engine.Dispose();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            folderBrowserDialog1.Description = "選擇模板檔案儲存位置";
            if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
            {
                string template = AppDomain.CurrentDomain.BaseDirectory + "點位模板.xlsx";
                string filePath = Path.Combine(folderBrowserDialog1.SelectedPath, "點位模板.xlsx");
                if (File.Exists(filePath))
                {
                    if (MessageBox.Show("模板檔案已存在,是否覆蓋?", "提示", MessageBoxButtons.OKCancel) == DialogResult.OK)
                    {
                        File.Copy(template, filePath, true);
                        MessageBox.Show("下載完成");
                    }
                }
                else
                {
                    File.Copy(template, filePath, true);
                    MessageBox.Show("下載完成");
                }
            }
        }
    }
}