在 tmap 中使用外部图标

作为一个强大的专题地图绘制包,**tmap** 确实让使用 R 语言绘制地图变得非常方便。然而如果想要在地图中引入图标(通常是外部图片文件,目前仅支持 png 格式),官方文档并没有给出详细的用例,而且文档描述也语焉不详。经过一系列的摸索后,终于找到了设置图标的方法。

既然要设置图标,那绘制的数据一般是点数据,这里就以点数据为例。在函数 `tm_symbols()` 中有两个名称非常相近的参数:`shape` 和 `shapes` 。前者是用于指定形状的名称,相当于“颜色”,后者用于提供可用的形状选项,相当于“调色盘”,不妨称之为“**调形盘**”。比如,如果 `shape` 指定了 `"square"` ,那么函数会在 `shapes` 的值中寻找名称为 `"square"` 的形状,然后使用该形状绘制要素。

# 生成形状选项——调形盘

函数默认提供的形状都是 R 语言自带的几种形状(21-25),如果想要使用自定义的图标,那么就需要使用 `tmap_icons()` 函数生成一个“调形盘”。假设 `poi_sel` 是一批 POI 数据,其中有一个变量 `category`表示 POI 的类别,并且我们在当前工作目录的`icon/`文件夹下有一批与所有`category` 值一一对应的图标文件。那么下面的代码就可以生成一个调形盘。

```R
poi_category <- unique(poi_sel$category)
poi_icon_labels <- map_chr(poi_title_map[poi_category], ~ strsplit(.x, "\\.")[[1]][2])
category_icon_path <- map_chr(poi_category, ~ file.path("icon", sprintf("%s.png", .x)))
names(category_icon_path) <- poi_icon_labels
poi_icons <- tmap_icons(category_icon_path, width = 48, height = 48)
poi_icons
```

```plaintext
$iconUrl
 [1] "icon/公司企业.png"     "icon/超级市场.png"     "icon/综合市场.png"  
 [4] "icon/商场.png"         "icon/加油站.png"       "icon/丧葬设施.png"  
 [7] "icon/政府机关.png"     "icon/会展中心.png"     "icon/科研机构.png"  
[10] "icon/中餐厅.png"       "icon/外国餐厅.png"     "icon/快餐厅.png"  
[13] "icon/咖啡厅.png"       "icon/运动场馆.png"     "icon/小学.png"    
[16] "icon/中学.png"         "icon/高等院校.png"     "icon/停车场出入口.png"
[19] "icon/地铁站.png"       "icon/火车站.png"       "icon/充电站.png"  
[22] "icon/公园广场.png"     "icon/国家级景点.png"  

$iconWidth
 [1] 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48

$iconHeight
 [1] 48 48 45 48 48 46 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48

$iconAnchorX
 [1] 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24

$iconAnchorY
 [1] 24 24 22 24 24 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24

$iconNames
 [1] "Company"          "Supermarket"      "Market"           "ShoppingCenter"  
 [5] "GasStation"       "FuneralFacility"  "Government"       "ConventionCenter"
 [9] "Research"         "ChineseFood"      "ForeignFood"      "FastFood"    
[13] "Café"            "Gym"              "PrimarySchool"    "HighSchool"  
[17] "University"       "ParkingEntrances" "Subway"           "TrainStation"  
[21] "ChargingStation"  "Park"             "ScenicSpot"
```

可以看到,在这个代码中,我们从所有类别中生成了一个 `category_icon_path` 列表,并且通过 `names()` 函数设置了键名。那么,生成的 `poi_icons` 中就会出现一个 `iconNames` 的变量。传入 `tmap_icons()` 的参数仅仅是一个不具名的向量,那么 `poi_icons` 中也就不会出现 `iconNames` 这个变量。这一点会影响后续使用。

# 使用形状

通常情况下,如果我们使用外部 PNG 图片文件作为要素符号,那么应该不需要对其进行颜色的设置,只需要设置大小即可。**tmap** 包提供了一个函数 `tm_markers()` 专门用来处理这种情况。使用这个函数绘制出来的符号没有边框,使用 `tm_symbols()` 也可以调用我们生成的调形盘绘制符号,但是会在外面加上一个边框,用来表示 `col` 参数设置的变量。

使用调形盘进行形状绘制的方法也很简单,例如(数据 `poi_sel_en` 中的 `category` 变量根据 `poi_sel` 转换过来的,值与 `poi_icons$iconNames` 对应):

```R
base_map + tm_shape(poi_sel_en) + 
    tm_markers(shape = "category", shapes = poi_icons, size = 2, title.shape = "Category", icon.scale = 0.2) + 
    tm_layout(
        legend.outside = T,
        legend.outside.size = 0.2,
        legend.title.size = 2,
        legend.text.size = 1.4,
    ) +
    tm_compass(position = c("right", "top")) +
    tm_scale_bar(position = c("right", "bottom"))
```

可以看到,我们通过 `shape` 参数制定了用于改变形状的列,然后将调形盘通过参数 `shapes` 传入。那么该函数就会在 `poi_icons` 中找到名称与 `category` 变量值对应的形状,并使用该形状进行符号绘制。绘制出来的图形如下。

![data_poi_sel.png](/media/acd15635-17d3-49ce-a612-42aca11f10fb_data_poi_sel.png)

那么如果调形盘中不存在 `iconNames` 变量会怎么样呢?这时就要求我们传入 `shape` 的值或列是数值型的,函数会以该数值为索引找到形状,而不再是通过名称进行查找了。