Разбор путей SVG в R

Я пытаюсь взломать рабочий процесс R для анализа путей SVG, используя этот файл на эта веб-страница. Я сталкиваюсь с артефактами в расположении полученных полигонов:

Разбор путей SVG в R

Некоторые страны не согласуются со своими соседями - например, США / Канада, США / Мексика, Россия / азиатские соседи. Поскольку эффект затрагивает страны с более сложными полигонами, похоже, проблема связана с кумулятивным суммированием, но я не понимаю, в чем проблема в моем рабочем процессе, а именно:

  1. анализировать необработанный SVG как XML и извлекать все строки пути SVG
  2. анализировать отдельные строки пути с помощью nodejsмодуль svg-path-parser
  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.info(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 в R

Любая помощь приветствуется. Синтаксис пути SVG указан в w3 и резюмирован более кратко здесь.


Изменить (ответ на @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.info(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)

Я также отправил это в модуль svg-path-parser на github

geotheory 09.09.2018 11:57

Я не знаком с R, но мне кажется, что вы делите путь на группы, ища команды closepath, а затем берете первый moveto в каждой группе в качестве отправной точки для суммирования позиций для преобразования в абсолютные. Два источника ошибок: 1. Команды moveto, кроме первой, также могут быть относительными (к последней координате предыдущей группы). 2. Группы нельзя закрывать командой closepath. Надежнее было бы поискать открывающийся moveto.

ccprog 09.09.2018 17:42

Привет @ccprog. Я использую closepath для создания переменной grp (которая идентифицирует уникальные многоугольники), но она не играет никакой роли в анализе фактических координат. На самом деле я просто использую поле SVG relative, которое, как я понимаю, указывает, когда координаты являются относительными или абсолютными. С абсолютными кодами вы также должны учитывать команды H / V, которые наследуют неактивную координату из предыдущей точки.

geotheory 09.09.2018 17:54
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
872
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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.

Я сделал кодовый ключ, который максимально точно перекрывает исходные данные пути с вашим рендерингом. Данные пути были разделены на группы (одноугольники) и преобразованы в абсолютные с помощью 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. Хорошо бы сосредоточиться на таких деталях. Так что это правда, что я установил значения NA для переменной y команд H равными нулю. Но позже я отменяю это с помощью if (d2$code[i] == 'H') d2$y_adj[i] = d2$y_adj[i-1] - в основном наследую предыдущее значение y. Я включаю соответствующие разделы data.frame для d и d2 - добавлено к вопросу.

geotheory 09.09.2018 19:14

Я понимаю, но я абсолютно уверен, что виновата абсолютная команда H. Я добавил скриншот, чтобы доказать свою точку зрения.

ccprog 09.09.2018 19:42

Нет, абсолютная H исправляет (горизонтальная) ошибка. Посмотрите на предыдущий пункт: это 198., 143., но это должен быть 191.76,146.07. Вертикальная ошибка сохраняется. Если, кроме того, я учту вертикальную ошибку и перемещаю ваш рендеринг на dy = -3.6, самая первая точка данных пути совпадает. Насколько я могу судить, все остальные группы путей внутренне непротиворечивы, но чем ниже они находятся в данных пути, тем больше они отклоняются от левого нижнего угла.

ccprog 09.09.2018 21:08

Это потребует некоторого размышления. Скоро вернусь.

geotheory 09.09.2018 22:07

Я сделал кодовый ключ, который максимально точно перекрывает исходные данные пути с вашим рендерингом. Данные пути были разделены на группы (одноугольники) и преобразованы в абсолютные с помощью Inkscape. К сожалению, программа не может преобразовать их в полигональные примитивы, поэтому там все еще есть команды V и H.

ccprog 09.09.2018 22:11

Я запустил график с более высоким разрешением, который должен помочь прояснить точную природу аномалии - imgur.com/a/nsyDOnU

geotheory 09.09.2018 22:48

Теперь я нашел ошибку. Это однозначный ответ.

ccprog 10.09.2018 01:18

Ты. Маленький. Потрошитель. Это должно быть так! Собираюсь усложнить свой код, но займусь этим.

geotheory 10.09.2018 01:24

... и теперь я прокручиваю эта страница вниз и вижу, что у парсера есть опция для преобразования путей в абсолютные команды. Это должно облегчить вам жизнь.

ccprog 10.09.2018 01:28

Хо-хо, какой-то недосмотр! Хорошо, это все значительно упрощает. Вызов узла console.info становится console.info(JSON.stringify(parseSVG.makeAbsolute(parseSVG(d)‌​))), и я группируюсь с mutate(grp = (command == 'moveto') %>% cumsum).

geotheory 10.09.2018 01:53

Вы очень помогли ccprog - спасибо. Это было весело.

geotheory 10.09.2018 01:54

И это тоже полезно для меня, потому что, хотя мне не нужно рисовать Канаду, мне нужно разбирать пути SVG в R. Я только что кое-что узнал.

Phil van Kleur 29.10.2020 13:48

Другие вопросы по теме