Программирование

R: обрезанный многоугольник sf не заполняет график после проекции

Исправьте проблему в R ggplot2 sf: обрезанный полигон не заполняет область карты после проекции в EPSG:3035. Настройте coord_sf с xlim, ylim и expand=FALSE для плоского квадратного фона. Примеры кода с st_crop и st_bbox.

Почему мой обрезанный многоугольник не заполняет всю область графика после проецирования в R?

Я работаю с визуализацией геопространственных данных в R, используя пакеты sf, rnaturalearth и ggplot2. Раньше мой код корректно работал для создания карт с плоским фоном, который заполнял всю область графика. Однако с другими данными, но при той же структуре кода, теперь я вижу проблему, где фоновая карта имеет округлую форму вместо того, чтобы заполнять всю область.

Мой код для создания базовой карты:

r
library(rnaturalearth)
library(sf)

countries <- ne_countries(scale = "medium", returnclass = "sf") %>% st_transform("ESRI:54030")

Затем я обрезаю это с помощью ограничивающего прямоугольника (bounding box), чтобы соответствовать области моих данных, что приводит к созданию нового объекта с CRS EPSG:3035:

r
st_crs(countries_cropped)
# Система координат:
# Ввод пользователя: EPSG:3035

Мой код ggplot2 для визуализации:

r
library(ggplot2)

ggplot() + 
 geom_sf(data = countries_cropped, fill = "gray70", color = "gray60", size = 0.2) + 
 geom_sf(data = grid_ids %>% filter(!is.na(source_class)), aes(fill = source_class), color = NA) +
 coord_sf(crs = st_crs(3035))

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

Мои данные проецируются корректно (подтверждается тем, что мои красные квадраты остаются квадратами в правильном положении без искажений). Единственная проблема - это форма фоновой карты во второй версии.

Что может вызывать эту разницу в отображении обрезанного многоугольника после проецирования, и как я могу убедиться, что фон заполняет всю область графика в виде плоского, квадратного shape?

В R ggplot2 sf такое поведение чаще всего возникает потому, что прямоугольный bounding box или результат st_crop были заданы/выполнены в другой CRS (или в географических координатах), и при проецировании (например, в EPSG:3035 — LAEA Europe) этот прямоугольник превращается в изогнутую, «округлую» границу; coord_sf по умолчанию рассчитывает пределы панели по таким экстентам. Решение — обрезать или трансформировать данные в целевую CRS и явно задать xlim/ylim в той же CRS (или нарисовать прямоугольный полигон по st_bbox), а также использовать expand = FALSE, чтобы убрать padding. Ниже — подробные объяснения, чеклист и готовые фрагменты кода.


Содержание


Почему coord_sf может давать округлую границу при проекции

Коротко: прямоугольник в одной системе координат при преобразовании в другую — не обязательно останется прямоугольником. EPSG:3035 — это ETRS89 / LAEA Europe (азимутальная равноплощадная проекция), и при проецировании больших областей граница прямоугольной области в географических координатах может превратиться в кривую (иногда «круг» вокруг центра проекции). coord_sf в ggplot2 автоматически рассчитывает границы панели по экстентам слоёв и, если вы не задали явно xlim/ylim, результаты трансформации этих экстентов будут определять форму видимой области (см. документацию по coord_sf для деталей о поведении и аргументах) — https://ggplot2.tidyverse.org/reference/ggsf.html.

Что именно обычно происходит в вашем случае?

  • Вы обрезали (st_crop) с прямоугольным bbox, но этот bbox был в другой CRS (или вы обрезали в lon/lat и потом трансформировали).
  • После st_transform прямоугольник в lon/lat становится кривым в EPSG:3035.
  • coord_sf рассчитывает панель по этим экстентам и оставляет «кривую» форму фонового полигона — поэтому сеточные клетки «отрезаются» по кривой линии.

На практике та же проблема описана в обсуждении на StackOverflow и в руководствах по ggplot2 + sf; решение — устанавливать пределы явно или строить фон как прямоугольник в целевой CRS (пример решения на StackOverflow — https://stackoverflow.com/questions/79812595/cropped-polygon-sf-not-filling-plot-area-after-being-projected).


Использование coord_sf и st_bbox: простое исправление

Самое быстрое и надёжное исправление — вычислить bounding box в той же CRS, в которой вы хотите рисовать (EPSG:3035), и передать его в coord_sf через xlim/ylim, плюс убрать padding через expand = FALSE.

Пример (если countries_cropped уже в EPSG:3035):

r
library(sf)
library(ggplot2)
# убедиться, что CRS правильный
st_crs(countries_cropped) # должен быть EPSG:3035

# получить bbox (в той же CRS)
bb <- st_bbox(countries_cropped)

# явный plot с пределами в CRS EPSG:3035
ggplot() +
 geom_sf(data = countries_cropped, fill = "gray70", color = "gray60", size = 0.2) +
 geom_sf(data = grid_ids %>% filter(!is.na(source_class)), aes(fill = source_class), color = NA) +
 coord_sf(crs = st_crs(3035),
 xlim = c(as.numeric(bb["xmin"]), as.numeric(bb["xmax"])),
 ylim = c(as.numeric(bb["ymin"]), as.numeric(bb["ymax"])),
 expand = FALSE)

Пояснения:

  • xlim/ylim здесь заданы в единицах EPSG:3035. Именно поэтому важно, чтобы bbox был вычислен уже в этой CRS.
  • expand = FALSE отключает добавление ggplot2 «упаковочного» отступа вокруг экстентов, что гарантирует, что фон будет совпадать с указанным прямоугольником.
  • Документация coord_sf и примеры объясняют именно этот подход — https://ggplot2.tidyverse.org/reference/ggsf.html и практическая статья с примерами — https://r-spatial.org/r/2018/10/25/ggplot2-sf-2.html.

Если ваш bbox или AOI задан в географической системе (EPSG:4326), сначала преобразуйте bbox в 3035, затем используйте полученный bbox для crop/передачи в coord_sf. Пример:

r
# если у вас AOI в lon/lat
aoi_lonlat <- st_as_sfc(st_bbox(c(xmin = 10, ymin = 45, xmax = 30, ymax = 60), crs = 4326))
aoi_3035 <- st_transform(aoi_lonlat, 3035)
bb2 <- st_bbox(aoi_3035)

# теперь crop/plot в 3035
countries_3035 <- st_transform(countries, 3035)
countries_cropped <- st_crop(countries_3035, bb2)

Создание прямоугольного фонового полигона (альтернатива)

Если вы хотите гарантированно иметь «плоский» прямоугольный фон (а не сложную форму, полученную от множества стран), проще создать прямоугольник по bbox и отрисовать его первым слоем:

r
# bbox уже в 3035
bb <- st_bbox(countries_cropped)
bg_rect <- st_as_sfc(bb) # прямоугольный sfc_POLYGON в CRS bbox
bg_sf <- st_sf(geometry = bg_rect) # простой sf для фонового полигона

ggplot() +
 geom_sf(data = bg_sf, fill = "gray90", color = NA) +
 geom_sf(data = grid_ids %>% filter(!is.na(source_class)), aes(fill = source_class), color = NA) +
 geom_sf(data = countries_cropped, fill = NA, color = "gray60", size = 0.2) +
 coord_sf(crs = st_crs(3035), expand = FALSE)

Преимущество: вы полностью контролируете форму фона (это будет именно прямоугольник в CRS 3035), и тогда сетка не будет «следовать» за какими-либо изогнутыми границами.

Если ваша задача — просто заливка панели цветом (без географического фона), самый простой путь — использовать theme(panel.background = element_rect(…)), это быстрее и не требует геометрий.


Отладка: чеклист и частые ошибки

Быстрый список — проверьте последовательно:

  • CRSs всех слоёв:
  • st_crs(countries_cropped)
  • st_crs(grid_ids)
  • st_crs(your_AOI)
  • bbox и тип геометрии:
  • st_bbox(countries_cropped)
  • st_geometry_type(countries_cropped) # POLYGON / MULTIPOLYGON?
  • st_is_valid(countries_cropped)
  • Последовательность операций:
  • Вы делали st_crop в lon/lat, а затем st_transform? Или сначала st_transform, затем st_crop? Лучше обрезать/формировать bbox уже в CRS, в которой планируете рисовать.
  • Проверка влияния s2:
  • sf::sf_use_s2() # true/false
  • В редких случаях операции в географических координатах (lon/lat) с s2 дают отличающиеся результаты; решение — временно выключить s2 (sf::sf_use_s2(FALSE)) или работать в проектированной CRS.
  • Визуальная проверка bbox:
  • Постройте bbox как слой (st_as_sfc) и визуально убедитесь, что он прямоугольный в вашей проекции.
  • Если вы используете st_crop с bbox без CRS — передайте bbox как объект с crs или сначала st_set_crs.

Примеры команд для отладки:

r
st_crs(countries_cropped)
st_bbox(countries_cropped)
st_geometry_type(countries_cropped)
st_is_valid(countries_cropped)
sf::sf_use_s2()

Если после всех проверок фон всё ещё «округлый», попробуйте упростить воспроизведение: сохраните небольшой reproducible example (countries + AOI) и проверьте, воспроизводится ли поведение — это облегчит поиск причины.


Дополнительные замечания (s2, st_crop и последовательность трансформаций)

  • st_crop работает в системе координат входного объекта: если вы передаёте bbox в lon/lat, а объект уже в проектированной CRS — результат будет неожиданным. Всегда согласуйте CRS.
  • S2-движок (по умолчанию в sf) меняет поведение геометрических операций в географических координатах. Когда вы работаете в lon/lat и видите нестандартные формы после операций пересечения/обрезки, попробуйте временно отключить s2 или сразу перейти в проекцию для вычислений.
  • Документация и практические руководства по работе sf + ggplot2 полезны для понимания этих нюансов: официальная справка coord_sf и статьи по визуализации карт — https://ggplot2.tidyverse.org/reference/ggsf.html и https://r-spatial.org/r/2018/10/25/ggplot2-sf-2.html. Дополнительный контекст про st_crop и взаимодействие CRS — обсуждения на GIS StackExchange — https://gis.stackexchange.com/questions/230900/crop-simple-features-object-in-r.

Источники


Заключение

Ключ к решению — согласованность CRS и явное управление пределами отображения. Если ваш обрезанный многоугольник был создан или обрезан в другой CRS (или в lon/lat), то при проецировании в EPSG:3035 он вполне может выглядеть «округлым» — coord_sf отобразит именно такую форму. Практическое правило: работайте в той CRS, в которой рисуете (или преобразуйте bbox в целевую CRS), передавайте xlim/ylim в coord_sf и используйте expand = FALSE, либо создавайте прямоугольный фон через st_as_sfc(st_bbox(…)). Это надёжно вернёт плоский, квадратный фон и сохранит вашу сетку в ожидаемой квадратной компоновке.

Если хотите, пришлите минимальный reproducible пример (countries_cropped + grid_ids — пару геообъектов), и я помогу подогнать конкретный код.

Авторы
Проверено модерацией
Модерация
R: обрезанный многоугольник sf не заполняет график после проекции