【问题标题】:Parsing SVG paths in R在 R 中解析 SVG 路径
【发布时间】:2018-09-09 09:50:05
【问题描述】:

我正在尝试破解用于解析 SVG 路径的 R 工作流,在 this webpage 上使用 this file。我在定位生成的多边形时遇到了伪影:

一些国家不与邻国结盟 - 例如。美国/加拿大、美国/墨西哥、俄罗斯/亚洲邻国。由于效果会影响具有更复杂多边形的国家/地区,因此累积求和似乎可能是一个问题,但我不清楚问题出在我的工作流程中,即:

  1. 将原始 SVG 解析为 XML,并提取所有 SVG 路径字符串
  2. 使用nodejs's svg-path-parser module 解析单个路径字符串
  3. 将生成的 data.frames(结合了绝对坐标和相对坐标)处理成所有绝对坐标

我在这里使用 R(针对美国/加拿大)复制了完整的工作流程,并通过外部调用 nodejs:

require(dplyr)
require(purrr)
require(stringr)
require(tidyr)
require(ggplot2)
require(rvest)
require(xml2)
require(jsonlite)

# Get and parse the SVG
doc = read_xml('https://visionscarto.net/public/fonds-de-cartes-en/visionscarto-bertin1953.svg')

countries = doc %>% html_nodes('.country')
names(countries) = html_attr(countries, 'id')
cdi = str_which(names(countries), 'CIV') # unicode in Cote d'Ivoire breaks the code
countries = countries[-cdi]

# Extract SVG paths and parse with node's svg-path-parser module.
# If you don't have node you can use this instead (note this step might be the problem):
# d = read_csv('https://gist.githubusercontent.com/geotheory/b7353a7a8a480209b31418c806cb1c9e/raw/6d3ba2a62f6e8667eef15e29a5893d9d795e8bb1/bertin_svg.csv')

d = imap_dfr(countries, ~{
  message(.y)
  svg_path = xml_find_all(.x, paste0("//*[@id='", .y, "']/d1:path")) %>% html_attr('d')
  node_call = paste0("node -e \"var parseSVG = require('svg-path-parser'); var d='", svg_path,
                     "'; console.log(JSON.stringify(parseSVG(d)));\"")
  system(node_call, intern = T) %>% fromJSON %>% mutate(country = .y)
}) %>% as_data_frame()


# some initial processing
d1 = d %>% filter(country %in% c('USA United States','CAN Canada')) %>%
  mutate(x = replace_na(x, 0), y = replace_na(y, 0), # NAs need replacing
         relative = replace_na(relative, FALSE),
         grp = (command == 'closepath') %>% cumsum)  # polygon grouping variable

# new object to loop through
d2 = d1 %>% mutate(x_adj = x, y_adj = y) %>% filter(command != 'closepath')

# loop through and change relative coords to absolute
for(i in 2:nrow(d2)){
  if(d2$relative[i]){ # cumulative sum where coords are relative
    d2$x_adj[i] = d2$x_adj[i-1] + d2$x_adj[i]
    d2$y_adj[i] = d2$y_adj[i-1] + d2$y_adj[i]
  } else{ # code M/L require no alteration
    if(d2$code[i] == 'V') d2$x_adj[i] = d2$x_adj[i-1] # absolute vertical transform inherits previous x
    if(d2$code[i] == 'H') d2$y_adj[i] = d2$y_adj[i-1] # absolute holrizontal transform etc
  }
}

# plot result
d2 %>% ggplot(aes(x_adj, -y_adj, group = paste(country, grp))) +
  geom_polygon(fill='white', col='black', size=.3) +
  coord_equal() + guides(fill=F)

任何帮助表示赞赏。 SVG 路径语法在w3 中指定,并且更简洁地总结为here


编辑(回复@ccprog)

这是从svg-path-parser 返回的H 命令序列的数据:

  code  command                 x      y relative country   
  <chr> <chr>               <dbl>  <dbl> <lgl>    <chr>     
1 l     lineto              -0.91  -0.6  TRUE     CAN Canada
2 l     lineto              -0.92  -0.59 TRUE     CAN Canada
3 H     horizontal lineto  189.    NA    NA       CAN Canada
4 l     lineto              -1.03   0.02 TRUE     CAN Canada
5 l     lineto              -0.74  -0.07 TRUE     CAN Canada

这是d2 在循环之后的相同序列的样子:

  code  command                 x     y relative country      grp x_adj y_adj
  <chr> <chr>               <dbl> <dbl> <lgl>    <chr>      <int> <dbl> <dbl>
1 l     lineto              -0.91 -0.6  TRUE     CAN Canada    20  199.  143.
2 l     lineto              -0.92 -0.59 TRUE     CAN Canada    20  198.  143.
3 H     horizontal lineto  189.    0    FALSE    CAN Canada    20  189.  143.
4 l     lineto              -1.03  0.02 TRUE     CAN Canada    20  188.  143.
5 l     lineto              -0.74 -0.07 TRUE     CAN Canada    20  187.  143.

这看起来不好吗?当我查看 y_adj 的 H 和前几行的原始值时,它们是相同的 142.56


编辑 2:工作解决方案,感谢@ccprog

d = imap_dfr(countries, ~{
  message(.y)
  svg_path = xml_find_all(.x, paste0("//*[@id='", .y, "']/d1:path")) %>% html_attr('d')
  node_call = paste0("node -e \"var parseSVG = require('svg-path-parser'); var d='", svg_path,
                     "'; console.log(JSON.stringify(parseSVG.makeAbsolute(parseSVG(d))));\"")
  system(node_call, intern = T) %>% fromJSON %>% mutate(country = .y)
}) %>% as_data_frame() %>% 
  mutate(grp = (command == 'moveto') %>% cumsum)

d %>% ggplot(aes(x, -y, group = grp, fill=country)) +
  geom_polygon(col='black', size=.3, alpha=.5) +
  coord_equal() + guides(fill=F)

【问题讨论】:

  • 我也把这个提交到了github上的svg-path-parser模块
  • 我对 R 不熟悉,但对我来说,您似乎通过查找 closepath 命令将路径分组,然后将每个组中的第一个 moveto 作为起点累积转换为绝对位置的位置。错误的两个来源是:1。 moveto 命令,除了第一个,也可以是相对的(相对于前一组的最后一个坐标)。 2. 不能使用closepath 命令关闭组。搜索开头moveto会更可靠。
  • 嗨@ccprog。我确实使用closepath 创建变量grp(标识唯一多边形),但它在解析实际坐标时没有任何作用。事实上,我只是使用 SVG relative 字段,据我所知,它指定坐标是相对还是绝对。对于绝对代码,您还必须考虑 H/V 命令,这些命令继承了前一点的非活动坐标。

标签: r node.js svg


【解决方案1】:

看看你对加拿大的渲染,尤其是哈德逊海峡的南部海岸。有一个非常明显的错误。筛选路径数据,在原始数据中发现如下序列:

h-2.28l-.91-.6-.92-.59H188.65l-1.03.02-.74-.07-.75-.07-.74-.07-.74-.06.88 1.09

我已将您的渲染结果加载到 Inkscape 中,并在顶部绘制了路径的相关部分,箭头标记了绝对 H 命令绘制的段。 (z命令已被删除,这就是缺少段的原因。)很明显,其中某处的段太长了。

事实证明,绝对的H 更正之前的(水平)错误。看前面一点:是198., 143.,但应该是191.76,146.07。垂直误差保持在-3.6左右。

我制作了一个codepen,它尽可能精确地覆盖原始路径数据和您的渲染。路径数据已被分成(单多边形)组,并由 Inkscape 转换为绝对值。不幸的是,程序无法将它们转换为多边形图元,所以里面仍然有 V 和 H 命令。

它显示了这个:

  • 路径的起点匹配。
  • 绝对 H 命令描述的点具有匹配的水平值,但不是垂直的。 (它是整个路径中唯一的绝对命令。)
  • 每个路径组(多边形)本身似乎都是一致的,但除了 group0 之外,它们都已从预期位置移除。

我已经对该偏差进行了一些视觉测量(误差约为 0.05),它们最终给出了线索​​:

group01: 0.44,-0.73
group02: 0.84,-1.12
group03: 2.04,-1.44
group04: 2.94,-1.73
group05: 2.60,-1.86
group06: 3.14,-2.38
group07: 3.68,-2.54
group08: 4.03,-3.35
group09: 4.87,-2.97
group10: 6.08,-3.50 (begin)
group10: 0.00,-3.53 (end)
group11: 1.08,-1.95
group12: 2.05,-2.45
group13: 2.89,-2.84
group14: 3.64,-3.67
group15: 4.48,-3.44
group16: 4.04,-3.99
group17: 4.32,-3.08
group18: 4.75,-2.75
group19: 5.72,-2.95
group20: 5.40,-3.11
group21: 6.02,-2.95
group22: 6.63,-4.14
group23: 6.85,-5.00
group24: 7.14,-4.86
group25: 7.72,-4.39
group26: 8.65,-4.75
group27: 9.49,-4.39
group28: 10.20,-4.44
group29: 11.13,-4.58

您正在删除closepath 命令,然后计算下一组相对于最后一组的最后一个显式点的第一个点。但是closepath 实际上移动了当前点:回到最后一个moveto 命令的位置。这些可能,但不必相同。

我不能在R中给你一个现成的脚本,但你需要做的是:在一个新组的开头,缓存第一个点的位置。在下一组开始时,计算相对于该缓存点的新第一个点。

【讨论】:

  • 感谢 ccprog 的帮助。很好地关注这样的细节。所以我确实将H 命令的y 变量的NA 值设置为零。但后来我用if(d2$code[i] == 'H') d2$y_adj[i] = d2$y_adj[i-1] 覆盖了它——基本上继承了以前的y 值。我包括dd2 的相关data.frame 部分 - 添加到问题中。
  • 我明白,但我绝对确定罪魁祸首是绝对的 H 命令。我添加了一个截图来证明我的观点。
  • 不,绝对 H 纠正(水平)错误。看前面一点:是198., 143.,但应该是191.76,146.07。垂直误差仍然存在。此外,如果我考虑了垂直误差并将您的渲染上移 dy=-3.6,则路径数据的第一个点匹配。据我判断,所有其他路径组在内部是一致的,但是它们在路径数据中越往下,它们越靠近左下角。
  • 这需要一些思考。很快就会回来。
  • 我制作了一个codepen,它尽可能精确地覆盖原始路径数据和您的渲染。路径数据已被分成(单多边形)组,并由 Inkscape 转换为绝对值。不幸的是,程序无法将它们转换为多边形图元,所以那里仍然有 V 和 H 命令。
猜你喜欢
  • 2019-03-12
  • 1970-01-01
  • 2018-02-04
  • 2013-05-27
  • 2023-04-10
  • 2020-03-22
  • 2014-01-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多