文章同步發表至 Medium
縣市找景點的完整功能描述如下:
- 身為一個使用者,我可以點擊地圖上的查詢按鈕,選擇縣市之後找到該縣市的景點。
 - 身為一個使用者,查詢縣市景點之後,地圖上只會呈現該縣市的景點。
 
從後端的角度來看,第一點是我們要做到的事情,所以首先,我們來檢視一下資料庫裡有沒有儲存到相關的縣市資訊:

這是我們在前幾天寫出來的 API,直接撈取資料庫的所有資料然後回傳——看起來除了 XY 座標和 address 之外,我們沒有任何和縣市有關的資訊。假設今天我們不只要做縣市,還需要細分到鄉鎮市區的時候,純文字、沒有固定格式的 address,要如何特別找到綠島鄉呢?
因此,我們要從資料庫裏面的 Geometry 著手,利用縣市的範圍找到交集,決定哪些景點在使用者選取的縣市內。
這個時候,政府資料開放平台就派上用場了。裡面由國土測繪中心所提供的直轄市、縣市界線是一個很好用的素材,可以讓我們藉由讀取檔案,找到縣市的 Geometry,接著再利用 NetTopologySuite 去針對我們資料庫裡的資料作處理。
在使用之前要注意:我們的資料所儲存的座標系統是 4326,國土測繪中心所提供的是 以 TWD97 為基準的 Shapefile,所以要先使用 QGIS Export Feature As 的功能,匯出成 4326 再繼續使用喔。
步驟很簡單,讀取縣市界線的 Shapefile,找到的話就和景點的清單做比較,如果景點在這個縣市裡(Within),就撈出來:
/// <summary> 縣市裡的景點清單 </summary>
/// <param name="county">縣市代碼</param>
[HttpGet("{county}")]
public IActionResult ScenicSpotInCounty(string county)
{
    // 建立一個空的 Geometry
    Geometry? countyGeometry = null;
    
    // 讀取 Shapefile
    foreach (var feature in Shapefile.ReadAllFeatures(@"C:\Users\user\Downloads\mapdata202209220943\COUNTY_MOI_1090820_4326.shp"))
    {
        // 如果 COUNTYID 的欄位等於傳入的縣市代碼則記錄下這筆 Geometry
        if (feature.Attributes["COUNTYID"].ToString() == county)
        {
            countyGeometry = feature.Geometry;
            break;
        }
    }
    
    // 處理找不到縣市代碼的情況
    if (countyGeometry == null) throw new KeyNotFoundException($"找不到代碼為 {county} 的縣市");
    
    var data = _db.ScenicSpots
        .Where(s => s.Geom.Within(countyGeometry))
        .Select(s => new ScenicSpotInfo()
        {
            Id = s.Id,
            Name = s.Name,
            Telephone = s.Telephone ?? "",
            Address = s.Address ?? "",
            X = s.Geom.Centroid.X,
            Y = s.Geom.Centroid.Y,
            Type = s.Type ?? "未分類",
            Park = s.Park ?? "",
            Info = s.Info ?? "暫無詳細資訊",
            UpdateTime = s.UpdateTime
        }).ToList();
    
    return Ok(data);
}
如果比較喜歡使用 Contains 的話,請記得把第 24 行修改一下喔:
.Where(s => countyGeometry.Contains(s.Geom))