請利用下列數據統計各類土地利用類型的NDVI均值。NDVI值通常位於-1至1之間,請將該地區NDVI異常值發生的像元位置和具體數值,生成一個點shape文件。
數據:
(1) Landsat8地面反射率文件
LC08_L1TP_121041_121042_20190312_T1_sr_scale_mosaic_clip.tif
Landsat8的NDVI公式:NDVI = (Band5-Band4)/(Band5+Band4)
(2) 梅川江流域的土地利用圖及其說明文件
landuse30_clip.tif
landuse_lookup.csv
landuse_readme.txt
考察要點:
柵格文件的讀寫、矢量shape文件的讀寫(比須掌握)
Numpy的熟練應用,巧用meshgrid函數(比須掌握)
Matplotlib繪圖(本次作業可選用)
提交要求:
(1)基本的python代碼文件,(2)在ArcMap中,以土地利用圖爲背景、以SHAPE點爲前景(NDVI正的異常值點爲紅色,負的異常值點爲藍色)做一幅圖,拷屏(或導出map)生成一個jpg文件。如果大家積極主動,可以用matplotlib把土地利用圖和NDVI異常值shape文件疊置在一起,生成一個jpg文件。
(3)將上述兩個文件壓縮到一個rar文件中,命名爲"學號_姓名.rar"提交到課程網站,如:2019001002_zhangwentian.rar。
請大家獨立完成,作爲結課成績的一部分。
說明
landuse_lookup. csv 存儲了“土地利用編碼”和“SWAT內植被類型”的查找表。其中,
AGRL 農田
SWRN 草地
RNGB 灌叢
WATR 水體
URHD 建築用地
FRSD 落葉林
FRSE 常綠林
FRST 混交林
landuse_code swat-vegname
10 AGRL
11 AGRL
12 AGRL
30 SWRN
40 RNGB
60 WATR
61 WATR
63 WATR
80 URHD
90 URHD
211 FRSD
221 FRSE
230 FRST
代碼(python3):
# -*- coding: utf-8 -*-
"""
Created on Sat Oct 19 23:27:54 2019
@author: Depei Bai
"""
from osgeo import gdal,ogr,osr
import numpy as np
import os
import osgeo
#選中代碼,tab統一右進四格,shift+tab統一左退兩格
#--------------------------------------read data------------------------------------
#讀取土地利用數據
dataset_1 = gdal.Open("landuse30_clip.tif")
d1_samples = dataset_1.RasterXSize
d1_lines = dataset_1.RasterYSize
d1_bands = dataset_1.RasterCount
geotans_1 = dataset_1.GetGeoTransform()
proj_1 = dataset_1.GetProjection()
data_1 = dataset_1.ReadAsArray(0,0,d1_samples,d1_lines)
del dataset_1
#讀取OLI數據,計算NDVI圖像
dataset_2 = gdal.Open("LC08_L1TP_121041_121042_20190312_T1_sr_scale_mosaic_clip.tif")
d2_samples = dataset_2.RasterXSize
d2_lines = dataset_2.RasterYSize
d2_bands = dataset_2.RasterCount
geotans_2 = dataset_2.GetGeoTransform() #左上角像元的大地座標和圖像分辨率
# 如果圖像不含地理座標信息,默認返回值是:(0,1,0,0,0,1)
#In a north up image,
#padfGeoTransform即爲dataset.GetGeoTransform()返回的值
#padfGeoTransform[0],padfGeoTransform[3]指左上角點座標(x,y);
#padfGeoTransform[1]圖像在x方向的空間分辨率,padfGeoTransform[5]是圖像在y方向上的空間分辨率;
#padfGeoTransform[2]和padfGeoTransform[4]指正北方向旋轉角度
#如果圖像正上指北的,則這兩個參數爲0
#Fetches the coefficients for transforming between 列/行 (P,L) raster space, and projection coordinates (Xp,Yp) space
#Xp = padfTransform[0] + P*padfTransform[1] + L*padfTransform[2];
#Yp = padfTransform[3] + P*padfTransform[4] + L*padfTransform[5];
#————————————————
#版權聲明:本文爲CSDN博主「當空月」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
#原文鏈接:https://blog.csdn.net/yyt_enjoyvc/article/details/7828368
proj_2 = dataset_2.GetProjection() #指橢球體和投影方式
data_2 = dataset_2.ReadAsArray(0,0,d1_samples,d1_lines)#取了所有波段所有行列的數據
del dataset_2
#----------------------------------Calculate NDVI-------------------------------
NDVI = (data_2[4,:,:] * 1.0 - data_2[3,:,:] * 1.0)/(data_2[4,:,:] + data_2[3,:,:])
#----------------------------------calculate the meam_NDVI_value---------------
#每種地物類型統計其NDVI值的時候均需要取非異常值進行統計,剔除該類型中大於1和小於0的值
#np.where()無法在一個括號中單純添加多個條件,而是要用多個括號,& 或 |
#APPEND無返回值,因此不能用賦值符號
#list to numpy.array強制轉換時不能在原值的基礎上改,只能重新給到新的變量
#用作索引的數組必須是整型或者布爾型,np.array型是不能作爲下標的,元組也不可以
AGRL = np.where(((data_1 == 10)|(data_1 == 11)|(data_1 == 12))&(NDVI > -1)&(NDVI < 1))
mean_agrl = np.mean(NDVI[AGRL])
SWRN = np.where((data_1 == 30)&(NDVI > -1)&(NDVI < 1))
mean_swrn = np.mean(NDVI[SWRN])
RNGB = np.where((data_1 == 40)&(NDVI > -1)&(NDVI < 1))
mean_rngb = np.mean(NDVI[RNGB])
WATR = np.where(((data_1 == 60)|(data_1 == 61)|(data_1 == 63))&(NDVI > -1)&(NDVI < 1))
mean_watr = np.mean(NDVI[WATR])
URHD = np.where(((data_1 == 80)|(data_1 == 90))&(NDVI > -1)&(NDVI < 1))
mean_urhd = np.mean(NDVI[URHD])
FRSD = np.where((data_1 == 211)&(NDVI > -1)&(NDVI < 1))
mean_frsd = np.mean(NDVI[FRSD])
FRSE = np.where((data_1 == 221)&(NDVI > -1)&(NDVI < 1))
mean_frse = np.mean(NDVI[FRSE])
FRST = np.where((data_1 == 230)&(NDVI > -1)&(NDVI < 1))
mean_frst = np.mean(NDVI[FRST])
positive_outlier_index = np.where(NDVI > 1)
negative_outlier_index = np.where(NDVI < -1)
#---------------------------將異常值寫入shape文件中------------------------------
osgeo.gdal.SetConfigOption('GDAL_FILENAME_IS_UTF8','NO')#解決中文路徑
osgeo.gdal.SetConfigOption('SHAPE_ENCODING','gb2312')#解決SHAPE文件的屬性值
filename = 'outlier_point.shp'
driver = ogr.GetDriverByName('ESRI Shapefile')
if os.access(filename,os.F_OK):
driver.DeleteDataSource(filename)
#以上,如果文件存在,就刪除,重新創建
ds = driver.CreateDataSource(filename)
spatialref = osr.SpatialReference(proj_2) #空間參考
#如果直接寫spatialref = proj_2會提示錯誤
#TypeError: in method 'DataSource_CreateLayer', argument 3 of type 'OSRSpatialReferenceShadow *'
#因此要通過osr.SpatialReference將proj_2字符串轉爲類
#attention: 如果原空間參考是投影座標那就是4666548,535646這種,如果
#原空間參考是經緯度座標,則是12.4366655349731,-70.0561218261719這種
#不同的空間參考給出的原點處的位置參數也是不一樣的
geomtype = ogr.wkbPoint #shape類型是點
layer = ds.CreateLayer(filename[:-4],srs = spatialref,geom_type = geomtype)
#layer = ds.CreateLayer(filename[:-4],srs = spatialref,geom_type = geomtype)
#layer圖層的名稱即.shp之前的字符串,空間參考是spatialref,幾何對象是點
loc_p1_line = positive_outlier_index[0][0]#第一個點的行號
loc_p1_sample = positive_outlier_index[1][0]#列號
geo_p1_x = geotans_2[0] +loc_p1_sample*geotans_2[1] + loc_p1_line*geotans_2[2]
geo_p1_y = geotans_2[3] + loc_p1_sample*geotans_2[4] + loc_p1_line*geotans_2[5]
pntp1_wkt = 'POINT('+str(geo_p1_x)+' '+str(geo_p1_y)+')'
#attention:中間的是空格,不能是逗號
loc_p2_line = positive_outlier_index[0][1]
loc_p2_sample = positive_outlier_index[1][1]
geo_p2_x = geotans_2[0] +loc_p2_sample*geotans_2[1] + loc_p2_line*geotans_2[2]
geo_p2_y = geotans_2[3] + loc_p2_sample*geotans_2[4] + loc_p2_line*geotans_2[5]
pntp2_wkt = 'POINT('+str(geo_p2_x)+' '+str(geo_p2_y)+')'
loc_p3_line = positive_outlier_index[0][2]
loc_p3_sample = positive_outlier_index[1][2]
geo_p3_x = geotans_2[0] + loc_p3_sample*geotans_2[1] + loc_p3_line*geotans_2[2]
geo_p3_y = geotans_2[3] + loc_p3_sample*geotans_2[4] + loc_p3_line*geotans_2[5]
pntp3_wkt = 'POINT('+str(geo_p3_x)+' '+str(geo_p3_y)+')'
loc_p4_line = positive_outlier_index[0][3]
loc_p4_sample = positive_outlier_index[1][3]
geo_p4_x = geotans_2[0] + loc_p4_sample*geotans_2[1] + loc_p4_line*geotans_2[2]
geo_p4_y = geotans_2[3] + loc_p4_sample*geotans_2[4] + loc_p4_line*geotans_2[5]
pntp4_wkt = 'POINT('+str(geo_p4_x)+' '+str(geo_p4_y)+')'
loc_p5_line = positive_outlier_index[0][4]
loc_p5_sample = positive_outlier_index[1][4]
geo_p5_x = geotans_2[0] + loc_p5_sample*geotans_2[1] + loc_p5_line*geotans_2[2]
geo_p5_y = geotans_2[3] + loc_p5_sample*geotans_2[4] + loc_p5_line*geotans_2[5]
pntp5_wkt = 'POINT('+str(geo_p5_x)+' '+str(geo_p5_y)+')'
#負異常值座標求解
loc_n1_line = negative_outlier_index[0][0]
loc_n1_sample = negative_outlier_index[1][0]
geo_n1_x = geotans_2[0] + loc_n1_sample*geotans_2[1] + loc_n1_line*geotans_2[2]
geo_n1_y = geotans_2[3] + loc_n1_sample*geotans_2[4] + loc_n1_line*geotans_2[5]
pntn1_wkt = 'POINT('+str(geo_n1_x)+' '+str(geo_n1_y)+')'
#-------------------------------write geomlist--------------------------
#geomlist是一個裏面,裏面存放着字符串,因此需要將此六個點放入這個list中
#geomlist存放的是幾何形狀,點線面的幾何構成座標
geomlist = [pntp1_wkt,pntp2_wkt,pntp3_wkt,pntp4_wkt,pntp5_wkt,pntn1_wkt]
#---------------------write field list-------------------------------
#field list 是個列表,列表裏面是每一個feature的屬性,是個字典型,如下:
#{'name': 'FIPS_CNTRY', 'type': 4, 'width': 2, 'decimal': 0}
#因此只需將異常值點字段信息按照列表中字典的形式寫入這個列表供後面使用即可
fieldlist = [{'name':'outlier_pt','type':4,'width':4,'decimal':0},\
{'name':'code','type':4,'width':2,'decimal':0},\
{'name':'color','type':4,'width':4,'decimal':0}] #創建字段list
#字段列表裏每一個{}的內容即爲shape屬性表頭一行的名稱及其詳細規定
#type: 0 int ;1 longinteger ;2 float ; 3 double; 4 text;5 date
#name 字段名稱長度不能超過11個字母
#------------------------------------------------------------------
#本實驗添加了三個字段,一個是“異常點”,一個是編碼,一個是顏色
#用pi表示正異常值,i from 1 to n,代號1 ,顏色red
#用nj表示負異常值,j from 1 to m,代號-1,顏色blue
#python索引也是先行後列,大都是先行後列,只有IDL先
#---------------------write fieldlist into the lyer-------------------
for fd in fieldlist:
field = ogr.FieldDefn(fd['name'],fd['type'])
if 'width' in fd:
field.SetWidth(fd['width'])
if 'decimal' in fd:
field.SetPrecision(fd['decimal'])
layer.CreateField(field)
#-------------------------------write reclist-------------------------
#reclist爲每個幾何圖形對應三個字段fieldlist的具體值
#如第一個圖形outlierpoints叫p1,code爲1,顏色是紅色,是具體值
#如最後一個圖形outlierpoints叫n1,code爲-1,顏色是藍色,是具體值
#本實驗中的圖形均爲點
reclist = [{'outlier_pt':'p1','code':1,'color':'red'},\
{'outlier_pt':'p2','code':1,'color':'red'},\
{'outlier_pt':'p3','code':1,'color':'red'},\
{'outlier_pt':'p4','code':1,'color':'red'},\
{'outlier_pt':'p5','code':1,'color':'red'},\
{'outlier_pt':'n1','code':-1,'color':'blue'}]
#給出所創建好的圖形(geomlist)中的字段(fieldlist)的具體值(reclist)
#----------------------------------write shape------------------------
for j in range(len(reclist)):
feat = ogr.Feature(layer.GetLayerDefn())
geom = ogr.CreateGeometryFromWkt(geomlist[j])
feat.SetGeometry(geom)
for f_d in fieldlist:
feat.SetField(f_d['name'],reclist[j][f_d['name']])
layer.CreateFeature(feat)
ds.Destroy()
爲簡化過程,將其中的geomlist寫入過程和reclist寫入過程寫爲循環,如下:
(筆者很討厭沒有註釋的代碼,爲保證代碼可讀性,依然保留了全部註釋)
# -*- coding: utf-8 -*-
"""
Created on Sat Oct 19 23:27:54 2019
@author: Depei Bai
"""
from osgeo import gdal,ogr,osr
import numpy as np
import os
import osgeo
#選中代碼,tab統一右進四格,shift+tab統一左退兩格
#--------------------------------------read data------------------------------------
#讀取土地利用數據
dataset_1 = gdal.Open("landuse30_clip.tif")
d1_samples = dataset_1.RasterXSize
d1_lines = dataset_1.RasterYSize
d1_bands = dataset_1.RasterCount
geotans_1 = dataset_1.GetGeoTransform()
proj_1 = dataset_1.GetProjection()
data_1 = dataset_1.ReadAsArray(0,0,d1_samples,d1_lines)
del dataset_1
#讀取OLI數據,計算NDVI圖像
dataset_2 = gdal.Open("LC08_L1TP_121041_121042_20190312_T1_sr_scale_mosaic_clip.tif")
d2_samples = dataset_2.RasterXSize
d2_lines = dataset_2.RasterYSize
d2_bands = dataset_2.RasterCount
geotans_2 = dataset_2.GetGeoTransform() #左上角像元的大地座標和圖像分辨率
# 如果圖像不含地理座標信息,默認返回值是:(0,1,0,0,0,1)
#In a north up image,
#padfGeoTransform即爲dataset.GetGeoTransform()返回的值
#padfGeoTransform[0],padfGeoTransform[3]指左上角點座標(x,y);
#padfGeoTransform[1]圖像在x方向的空間分辨率,padfGeoTransform[5]是圖像在y方向上的空間分辨率;
#padfGeoTransform[2]和padfGeoTransform[4]指正北方向旋轉角度
#如果圖像正上指北的,則這兩個參數爲0
#Fetches the coefficients for transforming between 列/行 (P,L) raster space, and projection coordinates (Xp,Yp) space
#Xp = padfTransform[0] + P*padfTransform[1] + L*padfTransform[2];
#Yp = padfTransform[3] + P*padfTransform[4] + L*padfTransform[5];
#————————————————
#版權聲明:本文爲CSDN博主「當空月」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
#原文鏈接:https://blog.csdn.net/yyt_enjoyvc/article/details/7828368
proj_2 = dataset_2.GetProjection() #指橢球體和投影方式
data_2 = dataset_2.ReadAsArray(0,0,d1_samples,d1_lines)#取了所有波段所有行列的數據
del dataset_2
#----------------------------------Calculate NDVI-------------------------------
NDVI = (data_2[4,:,:] * 1.0 - data_2[3,:,:] * 1.0)/(data_2[4,:,:] + data_2[3,:,:])
#----------------------------------calculate the meam_NDVI_value---------------
#每種地物類型統計其NDVI值的時候均需要取非異常值進行統計,剔除該類型中大於1和小於0的值
#np.where()無法在一個括號中單純添加多個條件,而是要用多個括號,& 或 |
#APPEND無返回值,因此不能用賦值符號
#list to numpy.array強制轉換時不能在原值的基礎上改,只能重新給到新的變量
#用作索引的數組必須是整型或者布爾型,np.array型是不能作爲下標的,元組也不可以
AGRL = np.where(((data_1 == 10)|(data_1 == 11)|(data_1 == 12))&(NDVI > -1)&(NDVI < 1))
mean_agrl = np.mean(NDVI[AGRL])
SWRN = np.where((data_1 == 30)&(NDVI > -1)&(NDVI < 1))
mean_swrn = np.mean(NDVI[SWRN])
RNGB = np.where((data_1 == 40)&(NDVI > -1)&(NDVI < 1))
mean_rngb = np.mean(NDVI[RNGB])
WATR = np.where(((data_1 == 60)|(data_1 == 61)|(data_1 == 63))&(NDVI > -1)&(NDVI < 1))
mean_watr = np.mean(NDVI[WATR])
URHD = np.where(((data_1 == 80)|(data_1 == 90))&(NDVI > -1)&(NDVI < 1))
mean_urhd = np.mean(NDVI[URHD])
FRSD = np.where((data_1 == 211)&(NDVI > -1)&(NDVI < 1))
mean_frsd = np.mean(NDVI[FRSD])
FRSE = np.where((data_1 == 221)&(NDVI > -1)&(NDVI < 1))
mean_frse = np.mean(NDVI[FRSE])
FRST = np.where((data_1 == 230)&(NDVI > -1)&(NDVI < 1))
mean_frst = np.mean(NDVI[FRST])
positive_outlier_index = np.where(NDVI > 1)
negative_outlier_index = np.where(NDVI < -1)
#---------------------------將異常值寫入shape文件中------------------------------
osgeo.gdal.SetConfigOption('GDAL_FILENAME_IS_UTF8','NO')#解決中文路徑
osgeo.gdal.SetConfigOption('SHAPE_ENCODING','gb2312')#解決SHAPE文件的屬性值
filename = 'outlier_point.shp'
driver = ogr.GetDriverByName('ESRI Shapefile')
if os.access(filename,os.F_OK):
driver.DeleteDataSource(filename)
#以上,如果文件存在,就刪除,重新創建
ds = driver.CreateDataSource(filename)
spatialref = osr.SpatialReference(proj_2) #空間參考
#如果直接寫spatialref = proj_2會提示錯誤
#TypeError: in method 'DataSource_CreateLayer', argument 3 of type 'OSRSpatialReferenceShadow *'
#因此要通過osr.SpatialReference將proj_2字符串轉爲類
#attention: 如果原空間參考是投影座標那就是4666548,535646這種,如果
#原空間參考是經緯度座標,則是12.4366655349731,-70.0561218261719這種
#不同的空間參考給出的原點處的位置參數也是不一樣的
geomtype = ogr.wkbPoint #shape類型是點
layer = ds.CreateLayer(filename[:-4],srs = spatialref,geom_type = geomtype)
#layer = ds.CreateLayer(filename[:-4],srs = spatialref,geom_type = geomtype)
#layer圖層的名稱即.shp之前的字符串,空間參考是spatialref,幾何對象是點
#------------calculate geolocations of the outlier_points&write geomlist-------
geomlist = []
#geomlist是一個列表,裏面存放着座標字符串,因此需要將此六個點的字符串座標放入這個list中
#geomlist存放的是幾何形狀,點線面的幾何構成座標
for k in range(len(positive_outlier_index[0])):
loc_pk_line = positive_outlier_index[0][k]#第k個正異常值點的行號
loc_pk_sample = positive_outlier_index[1][k]#第k個正異常值點的列號
#根據左上角像元及geotrans參數求正異常值點的地理座標(或投影座標)
#而具體是地理座標還是投影座標則由原來的遙感圖像projection決定
#而本實驗proj_2爲投影座標,如某點座標爲38395m,295705m
#查看data_2座標爲PROJCS["WGS 84 / UTM zone 50N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID[...]]]]
geo_pk_x = geotans_2[0] +loc_pk_sample*geotans_2[1] + loc_pk_line*geotans_2[2]
geo_pk_y = geotans_2[3] + loc_pk_sample*geotans_2[4] + loc_pk_line*geotans_2[5]
pntpk_wkt = 'POINT('+str(geo_pk_x)+' '+str(geo_pk_y)+')'
#attention:中間的是空格,不能是逗號
geomlist.append(pntpk_wkt)
for x in range(len(negative_outlier_index[0])):
loc_nx_line = negative_outlier_index[0][x]#第n個負異常值點的行號
loc_nx_sample = negative_outlier_index[1][x]#第kn個負異常值點的列號
geo_nx_x = geotans_2[0] +loc_nx_sample*geotans_2[1] + loc_nx_line*geotans_2[2]
geo_nx_y = geotans_2[3] + loc_nx_sample*geotans_2[4] + loc_nx_line*geotans_2[5]
pntnx_wkt = 'POINT('+str(geo_nx_x)+' '+str(geo_nx_y)+')'
geomlist.append(pntnx_wkt)
#---------------------write field list-------------------------------
#field list 是個列表,列表裏面是每一個feature的屬性,是個字典型,如下:
#{'name': 'FIPS_CNTRY', 'type': 4, 'width': 2, 'decimal': 0}
#因此只需將異常值點字段信息按照列表中字典的形式寫入這個列表供後面使用即可
fieldlist = [{'name':'outlier_pt','type':4,'width':4,'decimal':0},\
{'name':'code','type':4,'width':2,'decimal':0},\
{'name':'color','type':4,'width':4,'decimal':0}] #創建字段list
#字段列表裏每一個{}的內容即爲shape屬性表頭一行的名稱及其詳細規定
#type: 0 int ;1 longinteger ;2 float ; 3 double; 4 text;5 date
#name 字段名稱長度不能超過11個字母
#------------------------------------------------------------------
#本實驗添加了三個字段,一個是“異常點”,一個是編碼,一個是顏色
#用pi表示正異常值,i from 1 to n,代號1 ,顏色red
#用nj表示負異常值,j from 1 to m,代號-1,顏色blue
#python索引也是先行後列,大都是先行後列,只有IDL先
#---------------------write fieldlist into the lyer-------------------
for fd in fieldlist:
field = ogr.FieldDefn(fd['name'],fd['type'])
if 'width' in fd:
field.SetWidth(fd['width'])
if 'decimal' in fd:
field.SetPrecision(fd['decimal'])
layer.CreateField(field)
#-------------------------------write reclist-------------------------
reclist = []
#reclist爲每個幾何圖形對應三個字段fieldlist的具體值
#如第一個點圖形outlierpoints叫p1,code爲1,顏色是紅色,是具體值
#如最後一個點圖形outlierpoints叫n1,code爲-1,顏色是藍色,是具體值
#本實驗中的圖形均爲點
#給出所創建好的圖形(geomlist)中的字段(fieldlist)的具體值(reclist)
for i in range(len(geomlist)):
if i<len(positive_outlier_index[0]):
attribute = {'outlier_pt':'p'+str(i),'code':1,'color':'red'}
reclist.append(attribute)
else:
attribute = {'outlier_pt':'n'+str(i),'code':-1,'color':'blue'}
reclist.append(attribute)
#----------------------------------write shape------------------------
for j in range(len(reclist)):
#給圖層創建圖形
feat = ogr.Feature(layer.GetLayerDefn())
#調入geomlist中所存的所有圖形的幾何座標
geom = ogr.CreateGeometryFromWkt(geomlist[j])
#給圖形賦予上述位置
feat.SetGeometry(geom)
#給圖形寫入字段具體值,由f_d[name]引導
for f_d in fieldlist:
feat.SetField(f_d['name'],reclist[j][f_d['name']])
#正式將圖形信息寫入圖層
layer.CreateFeature(feat)
ds.Destroy()
結果
版權歸作者 小白是哪個小白_ 所有,轉載、引用請註明鏈接出處。