R: обрезанный многоугольник sf не заполняет график после проекции
Исправьте проблему в R ggplot2 sf: обрезанный полигон не заполняет область карты после проекции в EPSG:3035. Настройте coord_sf с xlim, ylim и expand=FALSE для плоского квадратного фона. Примеры кода с st_crop и st_bbox.
Почему мой обрезанный многоугольник не заполняет всю область графика после проецирования в R?
Я работаю с визуализацией геопространственных данных в R, используя пакеты sf, rnaturalearth и ggplot2. Раньше мой код корректно работал для создания карт с плоским фоном, который заполнял всю область графика. Однако с другими данными, но при той же структуре кода, теперь я вижу проблему, где фоновая карта имеет округлую форму вместо того, чтобы заполнять всю область.
Мой код для создания базовой карты:
library(rnaturalearth)
library(sf)
countries <- ne_countries(scale = "medium", returnclass = "sf") %>% st_transform("ESRI:54030")
Затем я обрезаю это с помощью ограничивающего прямоугольника (bounding box), чтобы соответствовать области моих данных, что приводит к созданию нового объекта с CRS EPSG:3035:
st_crs(countries_cropped)
# Система координат:
# Ввод пользователя: EPSG:3035
Мой код ggplot2 для визуализации:
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 может давать округлую границу при проекции
- Использование coord_sf и st_bbox: простое исправление
- Создание прямоугольного фонового полигона (альтернатива)
- Отладка: чеклист и частые ошибки
- Дополнительные замечания (s2, st_crop и последовательность трансформаций)
- Источники
- Заключение
Почему 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):
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. Пример:
# если у вас 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 и отрисовать его первым слоем:
# 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.
Примеры команд для отладки:
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.
Источники
- Пример обсуждения и решение на Stack Overflow: https://stackoverflow.com/questions/79812595/cropped-polygon-sf-not-filling-plot-area-after-being-projected
- Документация по coord_sf (ggplot2): https://ggplot2.tidyverse.org/reference/ggsf.html
- Практическое руководство по работе sf + ggplot2 (r-spatial): https://r-spatial.org/r/2018/10/25/ggplot2-sf-2.html
- Обсуждение поведения st_crop на 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 — пару геообъектов), и я помогу подогнать конкретный код.