> ## Content Index
> Fetch the complete content index at: https://blog.anthea.day/llms.txt
> Use this file to discover other available public pages before exploring further.

# 歡迎來到 GIS 的世界－Day 23：旅遊規劃小專案匯入資料
- URL: https://blog.anthea.day/series-2022-ithome-23/
- Published: 2022-10-06T16:02:41.000Z
- Updated: 2026-11-06T19:36:19.000Z
- Author: Anthea / 芫荽
- Tags: #Migrated-1792241843742, #Import 2026-10-17 12:57, Coding, 2022 iThome 鐵人賽：歡迎來到 GIS 的世界

根據前一篇確定好檔案的座標系統之後，我們就來正式匯入資料吧。

我們可以根據[政府資料開放平臺](https://data.gov.tw/?ref=blog.anthea.day)上，[景點](https://data.gov.tw/dataset/7777?ref=blog.anthea.day)的頁面中所提供的[觀光資訊標準格式](https://media.taiwan.net.tw/Upload/%E8%A7%80%E5%85%89%E8%B3%87%E8%A8%8A%E6%A8%99%E6%BA%96%E6%A0%BC%E5%BC%8FV1.0.pdf?ref=blog.anthea.day)，把手上的 Shapefile 內容過濾出我們所需要的資訊，存入資料庫。

### 建立 Table

在建立之前，記得先把 PostGIS 引入，才能正確的使用 `geometry` 的型別喔：

```C#
-- 引入 PostGIS
CREATE EXTENSION postgis;

-- 建立 table，以景點為例
CREATE TABLE public.scenic_spot
(
    id character varying NOT NULL,                -- 代碼
    name character varying NOT NULL,              -- 名稱
    telephone character varying,                  -- 電話
    address character varying,                    -- 地址
    county character varying,                     -- 縣市
    town character varying,                       -- 鄉鎮市區
    x double precision NOT NULL,                  -- 主要出入口或主要建築物所在位置的經度
    y double precision NOT NULL,                  -- 主要出入口或主要建築物所在位置的緯度
    type integer,                                 -- 景點分類
    park character varying,                       -- 停車位資訊
    info character varying,                       -- 參觀資訊
    update_time timestamp without time zone,      -- 最後異動時間
    geom geometry NOT NULL,
    PRIMARY KEY (id)
);
```

### 讀取檔案

跟資料庫的溝通我選擇使用 EntityFrameworkCore 的方式，因此要進行的步驟總共有三個：

1. 將資料庫的 Table 轉換成 Model
2. 資料匯入

#### 1\. Table 轉換成 Model

打開 Terminal 的介面（Rider 快捷鍵： `Ctrl+Alt+1` ），移動到 `.csproj` 的同一層資料夾底下，輸入指令：

```
dotnet ef dbcontext scaffold "Host=localhost;Database=tourism;Username=postgres;Password=0000;" Npgsql.EntityFrameworkCore.PostgreSQL -o ./Models/Db -c TourismEntity -f
```

相關的參數前幾篇有解釋過了，這裡就不再贅述。要注意的是，如果沒有特別指定 `-f` 的話，在執行的時候可能會出現錯誤：

```
The following file(s) already exist in directory 'C:\Users\user\Documents\GitHub\tourism-import\tourism-import\Models\Db': TourismEntity.cs,ScenicSpot 
.cs. Use the Force flag to overwrite these files.
```

#### 2\. 資料匯入

```C#
using Microsoft.Extensions.Configuration;
using NetTopologySuite.IO.Esri;
using tourism_import;
using tourism_import.Models.Db;

var shapefilePath = @"shapefile path";
var scenicSpots = new List<ScenicSpot>();

var features = Shapefile.ReadAllFeatures(shapefilePath);
var scenicSpots = features.Select(f =>
{
    var attributes = f.Attributes.GetValues();
    return new ScenicSpot
    {
        Id = attributes[(int)EnumAttributeName.Id].ToString(),
        Name = attributes[(int)EnumAttributeName.Name].ToString(),
        Telephone = attributes[(int)EnumAttributeName.Tel].ToString(),
        Address = attributes[(int)EnumAttributeName.Add].ToString(),
        County = attributes[(int)EnumAttributeName.Region].ToString(),
        Town = attributes[(int)EnumAttributeName.Town].ToString(),
        X = attributes[(int)EnumAttributeName.Px].ToDouble(),
        Y = attributes[(int)EnumAttributeName.Py].ToDouble(),
        Type = attributes[(int)EnumAttributeName.Orgclass].ToString(),
        Park = attributes[(int)EnumAttributeName.Parkin_nfo].ToString(),
        Info = attributes[(int)EnumAttributeName.Ticketinfo].ToString(),
        UpdateTime = attributes[(int)EnumAttributeName.Changetime].ToDateTime(),
        Geom = f.Geometry,
    };
}).ToList();

var db = new TourismEntity();
await db.ScenicSpots.AddRangeAsync(scenicSpots);
await db.SaveChangesAsync();

// Shapefile 欄位對應位置
public enum EnumAttributeName
{
    Id = 0,            // 代碼
    Name = 1,          // 名稱
    Tel = 3,           // 電話
    Add = 4,           // 地址
    Region = 6,        // 縣市
    Town = 7,          // 鄉鎮市區
    Px = 16,           // 主要出入口或主要建築物所在位置的經度
    Py = 17,           // 主要出入口或主要建築物所在位置的緯度
    Orgclass = 18,     // 景點分類
    Parkin_nfo = 24,   // 停車位資訊
    Ticketinfo = 27,   // 參觀資訊
    Changetime = 29,   // 最後異動時間
}
```

因為利用 `features.Attributes.GetValues()` 所讀取出來的資料都是文字，所以我們需要在第 21 行和第 22 行使用自定義的 `ToDouble()` 和第 26 行的 `ToDateTime()`，轉成先前資料庫所定義的格式。

```C#
public static class Extensions
{
    public static double ToDouble(this object attr)
    {
        return double.Parse(attr.ToString());
    }
    
    public static DateTime? ToDateTime(this object attr)
    {
        if (string.IsNullOrEmpty(attr.ToString())) return null;
        var dateTimeString = (attr.ToString()).Split('+')[0];
        var dateString = dateTimeString.Split('T')[0];
        var timeString = dateTimeString.Split('T')[1];
        var year = int.Parse(dateString.Split('-')[0]);
        var month = int.Parse(dateString.Split('-')[1]);
        var day = int.Parse(dateString.Split('-')[2]);
        var hour = int.Parse(timeString.Split(':')[0]);
        var minute = int.Parse(timeString.Split(':')[1]);
        var second = int.Parse(timeString.Split(':')[2]);
        return new DateTime(year, month, day, hour, minute, second);
    }
}
```

執行完成之後可以利用 pgAdmin 檢視一下資料：

![](https://cdn-images-1.medium.com/max/800/0*8irhbm_Yvm0Q0JBr.png)