-
Notifications
You must be signed in to change notification settings - Fork 18
/
09-SpatialData.qmd
1348 lines (973 loc) · 108 KB
/
09-SpatialData.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Пространственные данные {#spatial}
```{r setup-spatial, echo = FALSE, purl = FALSE, include=FALSE}
knitr::opts_knit$set(global.par = TRUE)
knitr::opts_chunk$set(warning = FALSE, message = FALSE, collapse = TRUE, out.width = '100%')
knitr::knit_hooks$set(crop = knitr::hook_pdfcrop)
```
## Предварительные требования {#spatial_prerequisites}
Данный модуль посвящен введению в работу с пространственными данными в R. Рассмотрены общие вопросы моделирования реального мира средствами моделей пространственных данных. Рассматривается чтение векторных и растровых данных, их визуализация стандартными средствами.
Необходимые для работы пакеты:
```{r}
library(sf)
library(stars)
library(mapview)
library(dplyr)
library(readr)
```
## Модели пространственных данных {#spatial_models}
**Пространственные данные** (spatial data) --- это данные о пространственных объектах и их наборах. В свою очередь, пространственный объект определяется как *цифровая модель материального или абстрактного объекта реального или виртуального мира с указанием его идентификатора, координатных и атрибутивных данных* [^09-spatialdata-1].
[^09-spatialdata-1]: ГОСТ Р 52438-2005 \<\<Географические информационные системы. Термины и определения\>\>. В стандарте поясняется, что объектом может быть неподвижный или движущийся простой или сложный объект, явление, событие, процесс и ситуация. Моделируемый объект может относиться к территории, акватории, недрам и воздушному пространству Земли, околоземному космическому пространству, другим космическим телам и небесной сфере. В широком смысле под пространственным объектом в геоинформатике понимается как сам объект, так и адекватная ему цифровая модель
Если говорить по сути, то пространственные данные можно определить как *данные о географических объектах или явлениях, фиксирующие их местоположение и/или распределение в системе координат, привязанной к телу Земли или любого другого небесного тела*. Таким образом, отличительной особенностью пространственных данных перед непространственными является координатное описание местоположения. На профессиональном жаргоне пространственные данные также часто называют **геоданными**. Следует помнить, что этот термин не является научным, и его не следует использовать в публикациях и квалификационных работах.
Важно знать отличия между векторной и растровой моделью пространственных данных.
**Векторная модель** пространственных данных включает описание координатных данных пространственных объектов и, опционально, топологических отношений между ними. Векторные данные фиксируют местоположение и форму объектов в виде геометрических примитивов, таких как точки, линии, полигоны, объемные тела. Выбор модели объекта (например, представить город точкой или полигоном) зависит от масштаба анализа и целей исследования. Векторная модель данных является объектно-ориентированной.
**Растровая модель** описывает не объекты, а пространственное распределение некоторой (выбранной исследователем) характеристики. Пространство разбивается регулярной сеткой ячеек, в каждой ячейке фиксируется значение исследуемого параметра (путем статистического осреднения, семплирования в центре ячейки и т.п.). Растровые данные могут быть как количественными (например, поле температуры), так и качественными (например, растр классифицированного снимка, каждая ячейка которого фиксирует принадлежность к тому или иному типу объекта). Таким образом, растровая модель является пространственно-ориентированной (или феномен-ориентированной).
Существуют и другие модели пространственных данных, однако их рассмотрение выходит за рамки настоящей лекции.
В настоящей лекции мы познакомимся с чтением и визуализацией пространственных данных в векторном и растровом формате, а также рассмотрим вопросы связанные с использованием картографических проекций.
## Векторные данные {#vector_data_r}
### Simple Features {#simple_features}
**Simple Features** (официально *Simple Features Access*) --- это стандарт [OGC 06-103](http://www.opengeospatial.org/standards/sfa), разработанный Open Geospatial Consortium (OGC) и реализованный также в виде международного стандарта [ISO 19125](https://www.iso.org/standard/40114.html), который определяет общую модель хранения и доступа к векторным объектам (точка, линия, многоугольник, мульти точечные, мультилинии и т. д.), в географических информационных системах.
Геометрическое представление пространственных объектов базируется на следующих принципах:
- Все геометрии состоят из точек.
- Точки являются координатами в 2-, 3- или 4-мерном пространстве.
- Все точки в геометрии имеют одинаковую размерность.
В дополнение к координатам $X$ и $Y$ имеются два дополнительных дополнительных параметра:
- координата $Z$, обозначающая высоту
- координата $M$, обозначающая некоторую меру, связанную с точкой, а не с признаком в целом (в этом случае это будет атрибут объекта). Измерение $M$ может быть использовано, например, для представления времени или линейных координат (для маршрутов).
Координаты простой геометрии всегда содержат компоненты $X$ и $Y$, поэтому все разнообразие возможных представлений определяется наличием или отсутствием дополнительных измерений $Z$ и $M$. Таким образом, получаем **четыре** варианта геометрии:
- двумерные точки $XY$
- трехмерные точки $XYZ$
- трехмерные точки $XYM$
- четырехмерные точки $XYZM$
В случае использования широт и долгот $X$ соответствует долготе, $Y$ соответствует широте.
Всего стандарт **Simple Features** включает в себя 17 типов геометрий. Из них наиболее употребительными являются следующие 7:
| Тип | Описание |
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `POINT` | нуль-мерная геометрия, содержащая одну точку |
| `LINESTRING` | последовательность точек, соединенных прямыми, несамопересекающимися отрезками; одномерная геометрия |
| `POLYGON` | геометрия с положительной площадью (двумерная); последовательность точек, отрезки между которыми формируют замкнутое кольцо без самопересечений; первое кольцо является внешним, ноль и более остальных колец представляют дырки внутри полигона |
| `MULTIPOINT` | множество точек; геометрия типа `MULTIPOINT` называется *простой* если ни одна пара точек в `MULTIPOINT` не совпадает |
| `MULTILINESTRING` | множество линий |
| `MULTIPOLYGON` | множество полигонов |
| `GEOMETRYCOLLECTION` | множество геометрий произвольного типа за исключением `GEOMETRYCOLLECTION` |
Примеры различных видов геометрий представлены на рисунке ниже:
```{r, echo = FALSE}
p = st_point(c(0.5,0.5))
pc = rbind(c(0.5,0.5), c(1, 3), c(2, 1), c(0.2, 2), c(2, 3), c(1.5, 1.5))
mp = st_multipoint(pc)
s1 = rbind(c(0,1),c(0.5,1.5),c(1.2,1.2),c(2,1.3),c(3,2))
ls = st_linestring(s1)
s1 = rbind(c(0.5,1.5),c(1.2,1.2),c(2,1.3))
s2 = rbind(c(0,1.5),c(0.5,2.0),c(1.2,1.7))
s3 = rbind(c(2,1.8),c(3,2.5))
mls = st_multilinestring(list(s1,s2,s3))
p1 = rbind(c(0.5,0.5), c(2,0), c(3,2), c(1.5,4), c(0,3), c(0.5,0.5))
p2 = rbind(c(1,1), c(0.8,2), c(2,2.2), c(1.4,1.1), c(1,1))
pol = st_polygon(list(p1, p2))
p3 = rbind(c(3,3.3), c(3.5, 3.1), c(4,3), c(4,3.7), c(3.7, 3.96), c(3.2,4), c(3,3.3))
p4 = rbind(c(3.2,3.4), c(3.8,3.2), c(3.8,3.7), c(3.3,3.8), c(3.2,3.4))
p5 = rbind(c(3,1.2), c(2.5,0.2), c(3.5,0.2), c(3.5,1.2), c(3,1.2))
p6 = rbind(c(0,1), c(0.1,0.8), c(0.2,0.5), c(0.1,0.3), c(0, 0.7), c(0,1))
mpol = st_multipolygon(list(list(p1,p2), list(p3,p4), list(p5), list(p6)))
gc = st_geometrycollection(list(pol, mp + c(3, -1), mls + c(2.5,2)))
```
```{r, echo=FALSE,fig.width=16, fig.height=8, fig.show='hold'}
par(mfrow = c(2,3))
plot(p, cex = 5, pch = 20, main = 'POINT', cex.main=3)
plot(ls, lwd = 3, main = 'LINESTRING', cex.main=3)
plot(pol, lwd = 3, col = 'grey', main = 'POLYGON', cex.main=3)
plot(mp, cex = 5, pch = 20, main = 'MULTIPOINT', cex.main=3)
plot(mls, lwd = 3, main = 'MULTILINESTRING', cex.main=3)
plot(mpol, lwd = 3, col = 'grey', main = 'MULTIPOLYGON', cex.main=3)
# plot(gc, lwd = 3, col = 'grey', border = 'black', main = 'GEOMETRYCOLLECTION', cex.main=3)
par(mfrow = c(1,1))
```
Оставшиеся виды геометрий *Simple Features* включают: `CIRCULARSTRING`, `COMPOUNDCURVE`, `CURVEPOLYGON`, `MULTICURVE`, `MULTISURFACE`, `CURVE`, `SURFACE`, `POLYHEDRALSURFACE`, `TIN`, `TRIANGLE`.
Существует два официально закрепленных формата представления SF: *Well-Known Text (WKT)* и *Well-Known Binary (WKB)*, которые необходимы для чтения таких данных человеком и машиной соответственно.
**Well-Known Text (WKT)** --- стандарт представления геометрии в виде множества списков координат, в которых координаты вершин разделены пробелами, вершины разделены запятыми, а компоненты полигонов и мультигеометрий заключены в круглые скобки и также разделены запятыми. Вышеприведенной картинке соответствуют следующие строки *WKT*:
```{r, echo=FALSE, collapse=T}
cat(st_as_text(p))
cat(st_as_text(ls))
cat(st_as_text(pol))
cat(st_as_text(mp))
cat(st_as_text(mls))
cat(st_as_text(mpol))
cat(st_as_text(gc))
```
**Well-Known Binary (WKB)** --- бинарный формат хранения координат. Именно этот формат фактически используется в базах данных, поскольку он обеспечивает высокую скорость чтения и записи данных (в отличие от текстового). Однако внешний вид данных в формате WKB мало о чем говорит человеку, поскольку он предназначен для чтения компьютером. Например, вышеприведенная строка `LINESTRING` будет выглядеть так:
```{r, echo=FALSE}
cat(st_as_binary(ls))
```
### Базовые библиотеки {#vector_data_packages}
В R существует высоко развитая инфраструктура для работы с векторными данными, которая обеспечивается пакетом [**sf**](https://cran.r-project.org/web/packages/sf/index.html).
Пакет **sf** базируется на библиотеках [PROJ](https://proj.org), [GDAL](https://gdal.org), [GEOS](https://trac.osgeo.org/geos/) и [S2](https://s2geometry.io), которые устанавливаются вместе с ним. Их назначение кратко описано на следующем рисунке:
```{r, echo=FALSE, fig.cap='Архитектура программных библиотек для работы с пространственными данными в R'}
knitr::include_graphics('images/sf_architecture_new.svg')
```
Со многими функциями **sf** мы познакомимся в последующих разделах нашего курса. Некоторые из них (такие как `arrange`, `filter`, `mutate` из пакета **dplyr**), должны быть уже знакомы вам по предыдущим лекциям. Можно обратить внимание на то, что практически все функции начинаются с префикса `st_`, что означает **"spatiotemporal"**. Данные префиксы были выбраны для унификации с аналогичными названиями функций, используемых в широко распространенной СУБД PostgreSQL для оперирования объектами *Simple Features*.
### Чтение {#vector_data_reading}
Существует большое количество форматов хранения пространственных данных. Но в общем и целом их можно разделить на две категории: файловые форматы (наиболее привычные пользователям) и хранение данных в СУБД --- системах управления базами данных. Благодаря библиотеке GDAL пакет **sf** имеет возможность читать и записывать [более 90 различных форматов векторных даных](http://www.gdal.org/ogr_formats.html).
Исторически наиболее распространенным форматом был (и остается) [ESRI Shapefile](https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf). Данный формат, однако не отвечает современным техническим требованиям с точки зрения гибкости, соответствия стандартам и возможностям хранения разнообразных типов геометрий (напомним, что в стандарте **Simple Features** их 17, а с учетом четырех вариантов размерности точек получается целых 68 ). Современный формат, который обеспечивает полную поддержку стандарта **Simple Features** (и не только) --- это [GeoPackage](http://www.geopackage.org). Именно его мы и будем использовать в нашем практикуме.
Для чтения данных средствами sf необходимо использовать функцию `st_read()`:
```{r, collapse = TRUE}
countries = st_read('data/ne/countries.gpkg')
```
Лог функции сообщил нам следующую информацию:
- Набор данных представляет собой коллекцию из 183 пространственных объектов с 72 атрибутами
- Тип геометрии `MULTIPOLYGON`
- Размерность геометрии $XY$
- Ограничивающий прямоугольник (разброс координат) по осям $X$ и $Y$ имеет диапазон $[-180, 180] \times [-90, 83.64513]$
- Проекция (CRS --- coordinate reference system) имеет название *WGS 84*.
Подгрузим также для работы данные по другим типам объектов:
```{r}
oceans = st_read('data/ne/oceans.gpkg')
rivers = st_read('data/ne/rivers.gpkg')
lakes = st_read('data/ne/lakes.gpkg')
cities = st_read('data/ne/cities.gpkg')
```
### Внутренняя структура {#sf_structure}
Традиционно во всех ГИС-приложениях и базах пространственных данных множество пространственных объектов представляется в виде таблицы атрибутов, где каждая строка соответствует объекту, а каждый столбец --- атрибуту объекта. С каждой строкой таблицы должна быть ассоциирована информация о геометрии объекта, которая, в зависимости от формата данных, может либо храниться непосредственно в таблице (в специальном столбце), либо быть вынесена в отдельную структуру данных, которая связана с таблицей атрибутов посредством ключа[^09-spatialdata-2].
[^09-spatialdata-2]: Например, в широко распространенном формате **Esri Shapefile** атрибутивная таблица хранится в файле `*.dbf` формата *DBASE*, геометрия хранится в отдельном файле `*.shp`, а связь между ними осуществляется через файл `*.shx`. Разбиение формата хранения на несколько файлов --- это одна из уязвимостей шейп-файлов: при отсутствии хотя бы одного из этих файлов данные прочесть стандартными средствами (без дополнительного хакинга) будет нельзя.
В **R** используется первый подход, в котором информация о геометрии хранится в специальном столбце таблицы. Каждая ячейка этого столбца соответствует геометрическому объекту *Simple Features*. Представление геометрических объектов реализовано стандартными средствами, такими как списки, матрицы и векторы. Эти структуры данных упорядоченным образом хранят координаты объектов и естественным образом соответствуют способу организации данных, который регламентируется стандартом *Simple Features*. Поскольку геометрический столбец хранит не обычные переменные, а структуры данных, он реализуется в виде так называемого *списка-колонки (list-column)*, каждый элемент которой соответствует отдельному объекту.
Исходя из этих соображений, представление пространственных объектов реализовано в **R** в виде иерархии из трех классов объектов:
1. `sf` (simple features) --- объект класса `data.frame`, представляющий множество пространственных объектов со списком-колонкой для хранения геометрии
2. `sfc` (simple features geometry column) --- список-колонка в объекте `sf`, представляющий множество геометрий пространственных объектов
3. `sfg` (simple feature geometry) --- геометрия пространственного объекта внутри списка `sfc`
В соответствии с перечисленными спецификациями происходит работа с пространственными объектами. То что, объекты типа Simple Features реализованы в виде самых обычных фреймов данных, означает что *любая операция, применимая к фрейму данных, будет также применима к объекту типа* `sf`. Это очень важная особенность объектов типа sf, которой сильно не хватало в экосистеме исторического пакета `sp`.
Посмотрим, как все это реализовано, на конкретном примере:
```{r, collapse = TRUE}
class(countries)
```
Данная форма записи говорит о том, что прочитанный слой имеет класс *sf*, который, в свою очередь, является расширением класса *data.frame*.
А теперь посмотрим на последние колонки в первых строках таблицы:
```{r}
head(countries[tail(colnames(countries))])
```
Видно, что геометрия пространственных объектов хранится в заключительном столбце с названием `geometry`. Данный столбец можно быстро извлечь, применив функцию `st_geometry()`. Полученный объект будет иметь тип **sfc** (Simple Feature Geometry Column)
```{r, collapse = TRUE}
outlines = st_geometry(countries)
class(outlines)
```
Полученный вывод говорит нам о том, что наши объекты имеют класс `sfc_MULTIPOLYGON`, который является расширением класса `sfc` (simple feature geometry column).
Теперь если просмотреть начало данных, то мы увидим, что это больше не фрейм данных, а аннотированный список:
```{r, collapse = TRUE}
head(outlines)
```
Далее можно опуститься на базовый уровень геометрии, получив доступ к отдельному объекту. Поскольку объект класса `sfc` представляет собой список, любой элемент можно извлечь по его порядковому номеру. Класс полученного объекта будет:
```{r, collapse = TRUE}
class(outlines[[8]])
```
Исходя из полученной информации можно сделать вывод, что геометрия 8-го объекта таблицы `countries` имеет класс `sfg`, реализованный в виде мультиполигонов (`MULTIPOLYGON`) с плоскими координатами (`XY`)
Наконец, чтобы добраться до координат в чистом виде, необходимо развернуть иерархию списков, из которых состоит объект `sfg`. Количество уровней вложенности всегда зависит от конкретного объекта, их может быть достаточно много, особенно если объекты представлены мультиполигонами (несколько компонент связности), каждый из которых также состоит из полигонов с дырками. В нашем случае все достаточно просто, так как в слое `countries` дырок в полигонах нет, а 8-й по счету полигон состоит из одной-единственной геометрии, координаты которой в виде матрицы можно извлечь как:
```{r, collapse = TRUE}
outlines[[8]][[1]]
```
### Визуализация {#sf_plotting}
#### Базовая графическая система {#sf_plotting_basic}
Если попытаться применить функцию `plot()` к геометрии объекта, она попытается нарисовать тематические карты по всем имеющимся атрибутам (но остановится, если их более 9):
```{r, collapse = TRUE, error=FALSE, warning=FALSE}
plot(countries)
```
Если задача стоит нарисовать границы объектов, то нужно отображать объект **sfc**:
```{r, collapse = TRUE, error=TRUE}
plot(outlines, col = 'red')
```
Для быстрого построения тематических карт по выбранному показателю необходимо при вызове функции `plot()` указать соответствующий атрибут фрейма данных:
```{r, collapse = TRUE}
plot(countries['sovereignt'], key.pos = NULL) # Здесь легенда не нужна
```
Для отображения координатной сетки надо указать параметр `graticule = TRUE`, а подписей координат --- `axes = TRUE`:
```{r}
plot(countries['gdp_md_est'], graticule = TRUE, axes = TRUE)
```
Для совмещения нескольких слоев на одной карте необходимо при втором и последующих вызовах функции `plot()` указать параметр `add = TRUE`. Все остальные настройки визуализации работают так же,как и в обычной графике:
```{r}
cities_large = cities |>
filter(scalerank == 0,
! name %in% c('Washington, D.C.', 'Paris', 'Riyadh', 'Rome', 'São Paulo', 'Kolkata'))
plot(st_geometry(countries), lwd = 0.5, border = 'gray')
plot(oceans, col = 'steelblue1', border = 'steelblue', add = TRUE)
plot(lakes, col = 'steelblue1', border = 'steelblue', add = TRUE)
plot(rivers, col = 'steelblue', add = TRUE)
plot(cities_large, col = 'black', pch = 19, cex = 0.25, add = TRUE)
text(cities_large$longitude, cities_large$latitude,
label = cities_large$name, cex = 0.5, pos = 2, offset = 0.25)
```
> **Внимание**: чтобы слои совместились на карте, они должна иметь одинаковую систему координат.
Ясно, что на полученных нами картах можно много что улучшить, однако это мы отложим до следующей главы, где подробно разбирается построение тематических карт в **R**.
> **Внимание**: чтобы слои данных можно было совместно анализировать и наносить на одну карту, они должны иметь одну и ту же координатную систему (проекцию).
#### Интерактивные карты {#spatial_interactive}
R предоставляет возможности для интерактивного просмотра пространственных данных средствами библиотек веб-картографирования. В данном разделе мы кратко познакомимся с возможностями пакета [**mapview**](https://r-spatial.github.io/mapview/), который использует возможности библиотеки [Leaflet](https://leafletjs.com/). Функции данного пакета не предназначены для создания тематических карт высокого качества и рассчитаны на выполнение исследовательского анализа данных.
Чтобы отобразить векторный или растровый слой средствами mapview, достаточно вызвать одноименную функцию данного пакета:
```{r, eval = FALSE}
mapview(countries)
```
```{r, echo = FALSE}
knitr::include_graphics('images/mapview1.png')
```
Чтобы отобразить определенный показатель, можно использовать параметр `zcol`, а палитру передать в параметр `col.regions`:
```{r, eval = FALSE}
nconts = length(unique(countries$continent))
mapview(countries, zcol = 'continent',
col.regions = RColorBrewer::brewer.pal(nconts, 'Set1'))
```
```{r, echo = FALSE}
knitr::include_graphics('images/mapview2.png')
```
Чтобы скомбинировать несколько слоев, необходимо сложить несколько вызовов `mapview()`:
```{r, eval=FALSE}
{ mapview(countries, zcol = 'continent',
col.regions = RColorBrewer::brewer.pal(nconts, 'Set1')) +
mapview(cities_large, col.regions = 'black', label = 'name', cex = 3) } |>
leafem::addStaticLabels(cities_large, label = cities_large$name,
offset = c(0.1, 0),
style = list("color" = "black", "font-weight" = "bold"))
```
```{r, echo = FALSE}
knitr::include_graphics('images/mapview3.png')
```
### Атрибутивные операции {#sf_attrs}
Поскольку пространственные объекты хранятся в фреймах данных, к ним можно применять стандартные операции выборки по атрибутам и преобразования таблиц. Например, можно выбрать Италию и отобразить ее на отдельной карте:
```{r, collapse = TRUE, message=FALSE, warning=FALSE}
italy = countries |>
filter(sovereignt == 'Italy')
plot(st_geometry(italy))
```
Следующий пример иллюстрирует как выбрать страны с населением более 100 млн человек:
```{r, collapse = TRUE, message=FALSE, warning=FALSE}
largest = countries |>
select(pop_est) |>
filter(pop_est > 100000000)
plot(outlines, col = 'lightgrey')
plot(largest, col = 'red', add = TRUE)
```
Обратите внимание на то, что при вызове функции `select()` столбец `geometry` не был указан в числе выбираемых переменных. Тем не менее, то, что мы смогли построить карту по результатам выборки, говорит о том, что данный столбец был сохранен. *Функции **dplyr** определены для объектов `sf` таким образом, чтобы всегда сохранять геометрический столбец.*
Еще интереснее работает агрегирование объектов по атрибутам. В случае, когда агрегируются пространственные объекты, необходимо объединять и их геометрию. При этом если у агрегируемых объектов имеется общая граница, ее необходимо удалить, а если объекты разнесены в пространстве, из них нужно собрать новый мульти-объект.
Например, мы можем агрегировать валовой региональный продукт по континентам:
```{r, collapse=TRUE, warning=F}
continents = countries %>% # этот пайп из пакета magrittr для подстановки в точку
filter(., st_is_valid(.)) |>
group_by(continent) |>
summarise(gdp = sum(gdp_md_est))
plot(continents['gdp'])
```
Потрясающе просто, не правда ли? Вдобавок, мы еще и получили границы континентов (достаточно условные, конечно), которых у нас раньше не было. Данный пример также показывает, что атрибутивные операции над пространственными объектами всегда учитывают их геометрию.
### Создание пространственных объектов {#sf_creation}
Пространственные объекты в R можно собирать "вручную", если есть такая необходимость. Например, вам известны координаты границ участков полевого обследования, полученные посредством GPS, а вам необходимо превратить их в полигоны, чтобы выполнить анализ и картографирование. Придется из координат собрать полигоны программным путем. Процесс создания пространственных объектов осуществляется в последовательности их иерархического соподчинения: **sfg** \> **sfc** \> **sf**.
#### Геометрические объекты (sfg) {#sf_sfg}
Для создания геометрических объектов в пакете sf существует ряд функций с говорящими названиями:
| Функция | Тип пространственного объекта |
|---------------------------|-------------------------------|
| `st_point()` | *POINT* |
| `st_linestring()` | *LINESTRING* |
| `st_polygon()` | *POLYGON* |
| `st_multipoint()` | *MULTIPOINT* |
| `st_multilinestring()` | *MULTILINESTRING* |
| `st_multipolygon()` | *MULTIPOLYGON* |
| `st_geometrycollection()` | *GEOMETRYCOLLECTION* |
В зависимости от типа создаваемого объекта, данные функции принимают координаты, организованные в виде одной из трех структур данных:
- Вектор координат (*POINT*)
- Матрица координат (*MULTIPOINT* или *LINESTRING*), в которой строки соответствуют точкам, столбцы --- координатам
- Список (для всех остальных типов)
Проще всего создаются отдельные **точки** (*POINT*):
```{r}
st_point(c(0, 2)) # XY POINT
st_point(c(0, 2, -1)) # XYZ POINT
st_point(c(0, 2, 5), dim = 'XYM') # XYM POINT
st_point(c(0, 2, -1, 5)) # XYZM POINT
```
Дополнительный параметр `dim=` служит для уточнения типа геометрии точек и по сути нужен только тогда, когда необходимо создать редко используемые точки типа *XYM*. во всех остальных случаях (*XY*, *XYZ*, *XYZM*) размерность геометрии распознается по умолчанию.
При создании **мультиточек** (*MULTIPOINT*) и **линий** (*LINESTRING*) необходимо подавать на вход функции уже матрицу координат:
```{r}
coords = matrix(c(
0, 2,
1, 3,
3, 1,
5, 0
), ncol = 2, byrow = TRUE)
mp = st_multipoint(coords) # XY MULTIPOINT
print(mp)
ls = st_linestring(coords) # XY LINESTRING
print(ls)
```
В первом случае геометрия состоит из отдельных точек. Во втором случае те же самые точки соединены линией:
```{r}
plot(ls)
plot(mp, col = 'red', pch = 19, add = TRUE)
```
Создание трех-(*XYZ*, *XYM*) и четырехмерных (*ZYXM*) мультиточек и линий выполняется аналогично, но матрица должна содержать не 2, а, соответственно 3 или 4 столбца, и при необходимости параметр `dim = 'XYM'`.
Создание **полигонов** (*POLYGON*), **мультиполигонов** (*MULTIPOLYGON*) и **мультилиний** (*MULTILINESTRING*) требует уже создания списков из матриц.
Почему нельзя представить обычный (не мульти) полигон просто матрицей координат? Потому что полигон может содержать дырки. Например, контур леса может содержать дырку в том месте, где находится озеро. Или озеро может содержать дырку в том месте, где находится остров. Природа предлагает нам бесконечное число таких примеров. В целях универсализации приходится закладываться на возможность наличия дырок в полигонах, поэтому даже полигоны без дырок представляются в виде списков. При этом действу.т следующее правила:
- Первая матрица координат в списке отвечает за контур полигона
- Все остальные матрицы координат отвечают за дыры в полигоне
- Координаты первой и последней точки в каждой матрице должны совпадать
Если дыр в полигоне нет, его список будет содержать только одну матрицу. Рассмотрим оба примера построения **полигонов**:
```{r}
coords = matrix(c( # Координаты главного полигона
1, 0,
0, 2,
2, 3,
4, 2,
3, 0.5,
1, 0
), ncol = 2, byrow = TRUE)
pol = st_polygon(list(coords)) # Простой полигон
print(pol)
plot(pol, col = 'lightblue')
hole = matrix(c( # Координаты дыры
2, 1,
3, 1.5,
3, 2,
2, 2,
1.5, 1.5,
2, 1
), ncol = 2, byrow = TRUE)
pol2 = st_polygon(list(coords, hole)) # Полигон с дырой
print(pol2)
plot(pol2, col = 'lightblue')
```
Мультиполигоны (*MULTIPOLYGON*) и мультилинии (*MULTILINESTRING*) требуются тогда, когда один и тот же географический объект состоит из нескольких геометрических объектов. Простейший пример --- островные государства. Чтобы представить страну, занимающую архипелаг (Багамские острова, Индонезия, Япония и т.д.) как один пространственный объект, необходимо создать мультиполигон. Все компоненты мультиполигона будут иметь общий набор атрибутов (непространственных характеристик). Мультилинии используются реже мультиполигонов и необходимы для представления линейных объектов, разорванных в пространстве. Примером такого объекта может быть любая река или канал, которые разорваны в тех местах, где они протекают через озеро или водохранилище, представленное полигональным объектом.
В мультиполигонах добавляется еще один уровень списка, то есть искомые матрицы координат будут располагаться как минимум на втором уровне вложенности:
```{r}
coords1 = matrix(c(
0.5, 0,
0, 1,
1, 1.5,
2, 1,
1.5, 0.25,
0.5, 0
), ncol = 2, byrow = TRUE)
coords2 = matrix(c(
3, 1,
2.5, 2,
3.5, 2.5,
4, 2,
4, 1.25,
3, 1
), ncol = 2, byrow = TRUE)
mpol = st_multipolygon(list(list(coords1), list(coords2)))
print(mpol)
plot(pol, col = 'grey') # Обычный полигон (серый)
plot(mpol, col = 'pink', add = TRUE) # Мультиполигон (розовый)
```
Как насчет острова на озере? Если остров и суша, окружающая озеро, составляют единое целое (например, подлежат учету как единый массив леса), их можно собрать как мультиполигон. В этом случае первая компонента мультиполигона будет представлять собой полигон с дыркой, а вторая компонента --- остров. Порядок компонент в данном случае роли не играет:
```{r}
coords4 = matrix(c(
2.2, 1.2,
2.8, 1.5,
2.8, 1.8,
2.2, 1.8,
2.0, 1.6,
2.2, 1.2
), ncol = 2, byrow = TRUE)
island = st_polygon(list(coords4))
mpol2 = st_multipolygon(list(pol2, island))
print(mpol2)
plot(mpol2, col = 'darkolivegreen4')
```
Из данного примера также видно, что при сборе мультиполигона на самом нижнем уровне вложенности можно подавать не списки матриц координат, а готовые полигоны.
Мультилиния, в отличие от мультиполигона, не требует дополнительного списка верхнего уровня, поскольку линии не могут содержать дыр. Например, можно собрать мультилинию из двух частей, соответствующих участкам реки до и после озера:
```{r}
coords1 = matrix(c(
-3, 0,
-1, 2,
0, 2
), ncol = 2, byrow = TRUE)
coords2 = matrix(c(
4, 2,
5, 3,
6, 5
), ncol = 2, byrow = TRUE)
mline = st_multilinestring(list(coords1, coords2))
print(mline)
plot(mline, lwd = 3, col = 'blue')
plot(pol2, col = 'lightblue', add = TRUE)
```
Наконец, еще один вид геометрии --- это геометрическая коллекция (GEOMETRYCOLLECTION), который позволяет хранить вместе любые виды геометрий. Эта возможность используется достаточно редко, тем не менее, рассмотреть ее нужно. Геометрическая коллекция собирается из списка объектов с простыми типами геометрии (мы создали их ранее):
```{r}
col = st_geometrycollection(list(ls, mp, mline, pol2))
print(col)
plot(col)
```
#### Списки геометрических объектов (sfc) {#sf_sfc}
Списки геометрических объектов (класс `sfc`) используются в таблицах пространственных объектов в качестве столбца, который хранит геометрию объектов. Создание таких списков осуществляется функцией `st_sfc()`, которой достаточно передать в качестве перечня параметров объекты типа `sfg`. Рассмотрим создание списка геометрий на примере точечных объектов (для остальных типов объектов порядок действий не меняется):
```{r}
moscow.sfg = st_point(c(37.615, 55.752))
irkutsk.sfg = st_point(c(104.296, 52.298))
petro.sfg = st_point(c(158.651, 53.044))
cities.sfc = st_sfc(moscow.sfg, irkutsk.sfg, petro.sfg)
print(cities.sfc)
```
При создании списка геометрий для него может быть определена система координат (это можно сделать и позднее при создании таблицы пространственных объектов). Для этого используем уже знакомую нам функцию `st_crs()`:
```{r}
st_crs(cities.sfc) = st_crs(4326) # WGS84
print(cities.sfc)
```
> Для списка геометрий может быть определена только одна система координат
Можно посмотреть, куда легли наши точки:
```{r}
plot(cities.sfc, pch = 19)
countries |>
filter(sovereignt == 'Russia') |>
st_geometry() |>
plot(add = TRUE)
```
#### Пространственные объекты (sf) {#sf_sf}
Пространственные объекты (класс `sf`) организуются в виде фрейма данных, один из столбцов которого имеет класс `sfc`. Для этого следует сначала создать обычный фрейм данных с атрибутами, а затем соединить его со списком геометрий посредством функции `st_sf`:
```{r}
city.attr = data.frame(
name = c('Москва', 'Иркутск', 'Петропавловск-Камчатский'),
established = c(1147, 1661, 1740),
population = c(12500, 620, 180)
)
cites.sf = st_sf(city.attr, geometry = cities.sfc)
print(cites.sf)
```
#### Точки по координатам {#sf_geom_points}
Достаточно распространенной является следующая задача: имеются координаты точек в табличной форме, необходимо создать на их основе набор пространственных объектов. Для решения этой задачи можно воспользоваться функцией `st_as_sf()`. Рассмотрим задачу на примере файла координат станций из базы метеорологических данных [**ВНИИГМИ-МЦД**](http://meteo.ru/):
```{r}
(stations = read_fwf('data/vniigmi/stations.txt',
col_positions = fwf_widths(diff(c(1, 7, 42, 47, 53, 59, 67, 71)),
col_names = c('id', 'name', 'lat', 't1', 'lon', 't2', 'z')),
locale = locale(encoding = 'CP1251')))
```
Теперь создадим пространственные точки на основе этой таблицы, взяв координаты из столбцов *lat* и *lon* соответственно и указав код системы координат:
```{r}
sf_stations = st_as_sf(stations, coords = c("lon", "lat"), crs = 4326)
plot(st_geometry(sf_stations), pch = 19, col = 'red', cex = 0.25)
plot(st_geometry(countries), border = 'grey', add = TRUE)
box()
```
#### Преобразование типов геометрии {#sf_cast}
Для преобразования типов геометрии существует функция `st_cast()`. Функция принимает объекты классов `sfg`, `sfc` или `sf`, а также название типа геометрии, к которому необходимо привести входные объекты. Довольно часто возникает задача конвертации площадного объекта в линейный и обратно, а также задача получения координат вершин линейного или площадного объекта в виде точек. Примеры преобразований:
```{r}
italy.borders = st_cast(italy, 'MULTILINESTRING')
class(st_geometry(italy.borders))
italy.regions = st_cast(italy.borders, 'MULTIPOLYGON')
class(st_geometry(italy.regions))
italy.points = st_cast(italy.borders, 'POINT')
class(st_geometry(italy.points))
plot(st_geometry(italy.regions), lwd = 0.5)
plot(italy.points, pch = 20, add = TRUE)
```
#### Полигонизация и разбиение линий {#sf_polygonize}
**Полигонизация** --- это процесс преобразования линии или мультилинии в полигон(ы). Полигон может быть образован последовательностью из одной и более линий, для которых выполняются следующие условия:
1. Каждая линия является простой (не имеет самопересечений)
2. Линии касаются только своими начальными и конечными точками
3. Линии образуют замкнутую последовательность (т.е. выйдя из любой конечной точки и двигаясь вдоль множества линий, можно вернуться в ту же точку.)
Полигонизация может применяться только к одному геометрическому объекту (simple feature geometry). Соответственно, это должна быть либо просто замкнутая линия, либо мультилиния, компоненты которой образуют замкнутую последовательность.
Рассмотрим операции полигонизации и добавления узлов на простом примере трех пересекающихся отрезков:
```{r}
# Создадим три линии
coords1 = rbind(c(0, 0), c(0, 6))
line1 = st_linestring(coords1)
coords2 = rbind(c(-1,1), c(5,1))
line2 = st_linestring(coords2)
coords3 = rbind(c(-1,5), c(4,0))
line3 = st_linestring(coords3)
# Создадим мультилинию
mls = st_multilinestring(list(line1, line2, line3))
plot(mls)
# Посмотрим на ее точки
points = st_cast(mls, 'MULTIPOINT')
plot(points, pch = 20, add = TRUE)
```
Из рисунка видно, что линии образуют треугольную замкнутую область. Также рисунок показывает, что у компонент мультилинии нет вершин в точках пересечения. Мы можем попытаться найти замкнутые области и превратить их в полигоны, используя `st_polygonize()`:
```{r}
st_polygonize(mls)
```
Операция завершилась возвратом пустой геометрической коллекции, то есть программа не смогла выделить замкнутые области. Это произошло по причине того, что линии не разбиты в точках пересечения. Разбить их на компоненты можно, используя функцию `st_node()`:
```{r}
mls2 = st_node(mls)
poly2 = st_polygonize(mls2)
points2 = st_cast(mls2, 'MULTIPOINT')
plot(mls2)
plot(poly2, col = 'grey', add = TRUE)
plot(points2, pch = 20, add = TRUE)
```
Таким образом, после разбиения линий на куски в точках пересечения стала возможной операция полигонизации.
### Геометрические атрибуты {#sf_geom_attrs}
К описательным характеристикам геометрии относятся ограничивающий прямоугольник, периметр (для линий и полигонов), площадь (для полигонов), центроид и список координат, которые можно получить с помощью функций `st_bbox()`, `st_length()`, `st_area()`, `st_centroid()` и `st_coordinates()` соответственно. Функции корректно работают для простых объектов, мультиобъектов, списков геометрий и пространственных объектов. Применительно к полигону Италии эти параметры будут учитывать части геометрии, занимаемые островами:
```{r}
st_bbox(italy) # Координаты органичивающего прямоугольника
st_area(italy) # Площадь
st_length(italy) # Периметр
st_centroid(italy) |> st_geometry() # Центроид (может быть не внутри для невыпуклых фигур)
st_point_on_surface(italy) |> st_geometry() # Точка гарантированно внутри, но не обязательно в центре
st_coordinates(italy) |> head() # Список координат
```
Обратите внимание на то, что площадь и периметр выводятся с указанием единиц измерений! Это возможно благодаря тому, что объекты типа `sf` поддерживают единицы измерений на основе пакета [units](https://cran.r-project.org/web/packages/units/index.html).
> Если данные находятся в плоской прямоугольной системе координат, то единицы измерения как правило указываются в параметрах проекции --- следовательно, они могут быть использованы при вычислении геометрических параметров объектов. Если же данные хранятся в широтах и долготах, то вычисление геометрических параметров осуществляется пакетом *sf* по формулам сферической тригонометрии через пакет [geosphere](https://cran.r-project.org/web/packages/geosphere/index.html). Это позволяет выводить результат в плоских единицах измерения.
Ограничивающий прямоугольник можно быстро преобразовать в полигон и нанести на карту, применив функцию `st_as_sfc()`:
```{r}
box = st_as_sfc(st_bbox(italy)) # Ограничивающий прямоугольник
plot(italy |> st_geometry(),
col = 'lightgrey')
plot(box,
border = 'orangered',
add = TRUE)
plot(st_centroid(italy),
col = 'darkgreen',
pch = 19,
add = TRUE)
plot(st_point_on_surface(italy),
col = 'steelblue4',
pch = 19,
add = TRUE)
```
Как видно, в данном случае центроид и характерная точка расположились относительно рядом. Однако так бывает далеко не всегда. Выполним аналогичные вычисления для Индонезии:
```{r}
indonesia = countries |> filter(sovereignt == 'Indonesia')
box = st_as_sfc(st_bbox(indonesia))
plot(indonesia |> st_geometry(),
col = 'lightgrey')
plot(box,
border = 'red',
add = TRUE)
plot(st_centroid(indonesia),
col = 'darkgreen',
pch = 19,
add = TRUE)
plot(st_point_on_surface(indonesia),
col = 'steelblue4',
pch = 19,
add = TRUE)
```
Как видно, в данном случае центроид мультиполигона оказался за пределами какой-либо из его полигональных компонент, в то время как характерная точка находится внутри одного из полигонов. Таким образом, если необходимо получить точку, находящуюся гарантированно в пределах исходного множества, следует использовать `st_point_on_surface()`. При этом следует помнить, что характерная точка, в отличие от центроида, может не располагаться в визуальном центре тяжести множества объектов, и выбор между этими способами описания геометрии остается за разработчиком.
### Экспорт {#sf_export}
Для экспорта векторных пространственных данных можно воспользоваться функцией `st_write()`, которая определит формат выходного файла по указанному вами расширению:
```{r, eval = FALSE}
st_write(cites.sf, 'data/mycities.shp') # Шейп-файл
```
## Растровые данные {#raster_data_r}
Работа с растровыми данными в целом гораздо проще, чем работа с векторными объектами. Это обусловлено в том числе жесткой сеточной структурой данных, которая предоставляет не так много свободы в различных сценариях обработки данных. В то же время, эта структура позволяет сделать растровые алгоритмы универсальными и робастными, многие задачи решаются в растровом виде быстрее и проще, чем в векторном.
### Теоретические сведения {#raster_data}
**Растр** представляет из себя матрицу значений. Каждой ячейке матрицы соответствует прямоугольная пространственная область фиксированного размера, которая называется *пикселом*. Различают растры *непрерывные* и *категориальные (классифицированные)*. Также необходимо разделять *одноканальные* и *многоканальные растры*. Примером одноканального растра является цифровая модель рельефа. В виде многоканальных растров часто представляют космические снимки.
В отличие от векторных данных, которые требуют указания координат для каждой вершины, регулярно-ячеистый характер растровой модели позволяет вычислять координаты пикселов на основе их индексов. Поэтому фактически растровые данные хранятся в виде линейно упорядоченного списка значений *(raster values)* и описания геометрии растра *(raster geometry)*.
**Геометрия растра** определяет, где именно располагаются в пространстве пикселы растра и может быть описана путем указания следующих компонент[^09-spatialdata-3]:
[^09-spatialdata-3]: Названия перечисленных компонент геометрии растра укоренились благодаря распространенности стандарта [Esri ASCII Grid](https://en.wikipedia.org/wiki/Esri_grid)
| Параметр | Назначение |
|-------------|--------------------------------------------------|
| `NCOLS` | Количество столбцов |
| `NROWS` | Количество строк |
| `XLLCENTER` | Координата $X$ центра левой нижней ячейки растра |
| `YLLCENTER` | Координата $Y$ центра левой нижней ячейки растра |
| `CELLSIZE` | Размер ячейки |
Иногда вместо параметров `XLLCENTER`/`YLLCENTER` указываются `XLLCORNER`/`YLLCORNER`, которые кодируют координаты левого нижнего угла, а не центра левой нижней ячейки растра. Выбор одного из двух этих вариантов определяет тип *регистрации растра*, а их значения указывают, в какое именно место необходимо "посадить" растр, чтобы его ячейки заняли соответствующие им области в системе координат. Если геометрия растра характеризуется *анизотропией*, то вместо одного значения `CELLSIZE` могут быть указаны разные размеры ячеек по осям координат `CELLSIZEX` и `CELLSIZEY`.
В отличие от векторной модели, которая позволяет хранить данные только о нужных географических локациях, растровая модель такой свободы не предоставляет. Матрица ячеек растра всегда покрывает область данных целиком, и за простоту растровой структуры приходится расплачиваться ее неэкономичностью. Поскольку часто данные имеются не на всю территорию, возникает необходимость кодирования ячеек, для которых данные не известны, специальным числом (назовем его условно `NODATA_VALUE`). Значение этого числа хранится в метаданных растра и позволяет интерпретировать соответствующие ячейки как пустые.
В настоящее время для работы с растровыми данными в R используются два пакета: [**stars**](https://r-spatial.github.io/stars/) и [**terra**](https://cran.r-project.org/web/packages/terra/index.html). **terra** является наследником пакета [raster](https://cran.r-project.org/web/packages/terra/index.html), который исторически был основным средством работы с растровыми данными и обладает широким спектром функций растрового анализа. **stars** --- относительно новый, разработан с целью поддержки многомерных данных и более тесного взаимодействия с пакетом `sf`. В целом можно сказать, что пакеты **terra** и **stars** частично пересекаются по функциональности, но скорее дополняют друг друга, нежели дублируют.
В этой и ближайших лекциях мы будем работать с растрами в формате **stars**, поскольку он концептуально близок к пакету **sf**.
### Чтение {#raster_read}
Для чтения растров любой размерности можно использовать функцию `read_stars()`:
```{r}
dem = read_stars('data/world/gebco.tif') # Цифровая модель рельефа
dem
img = read_stars('data/world/BlueMarbleJuly.tif') # Цветной космический снимок (RGB)
img
```
### Внутренняя структура {#stars_inner}
Для работы с данными типа stars необходимо понимать их внутреннюю структуру. Для начала можно взглянуть на нее посредством стандартной функции `str()`:
```{r}
str(img)
```
Видно, что данный трёхканальный растр представляет собой список из единственного элемента с названием `BlueMarbleJuly.tif` --- это имя было присвоено автоматически при чтении растра. Каждый такой элемент соответствует *переменной* данных. В данном случае переменная одна --- это интенсивность цвета. Хранится она в виде трехмерного массива (`array`) размерностью $720 \times 360 \times 3$:
```{r}
str(img[[1]])
img[[1]][100, 200, 2]
```
Каждой оси этого массива соответствует измерение (`dimension`), которое определяет параметры отображения индексов массива на соответствующую систему координат (пространственную, временную, спектральную и т.д.). Например, чтобы понять, что ячейка растра с индексами `[36, 18, ]` имеет географические координаты (широту и долготу) `(0, 0)`, нужно знать направления осей растра, размер ячейки и координаты одной из угловых ячеек растра. Необходимая информация находится в атрибуте `dimensions` объекта `stars`, т.е. является общей для всех переменных. При печати параметры измерений выводятся в удобном табличном виде:
```{r}
attr(img, 'dimensions')
```
Этот атрибут представляет собой список, длина которого равна количеству измерений в массиве данных переменной. Обычно измерения имеют имена, в данном случае это `x`, `y` и `band`. Описание каждого измерения выполнено по единому шаблону, который включает следующие параметры:
- `from`: начальный индекс (будет меняться при обрезке растра при постоянной точке отсчета индексов);
- `to`: конечный индекс (будет меняться при обрезке растра при постоянной точке отсчета индексов);
- `offset`: координата первого пиксела (точки отсчета);
- `delta`: размер ячейки;
- `refsys`: координатная (референцная) система: для систем счета координат, времени, высот и других измерений будет своя;
- `point`: логическое значение, которое указывает, следует ли интерпретировать элементы растра по этой оси как измеренные в точке (мгновенные) или агрегированные по площади (за временной период);
- `values`: значения координат ячеек по данной оси
- `NULL` (используется в большинстве случаев, т.к. координаты могут быть вычислены на основе `from`, `delta` и индекса пиксела),
- вектор координат (используется для представления ректилинейных растров в переменным размром пиксела),
- объект класса `intervals` (список из двух векторов --- начал и концов интервалов), or
- матрица координат такой же размерности, что и пространственные измерения растра. В случае стекущего примера будет иметь размер $720 \times 360$. Используется для представления *криволинейных* растров.
Например, посмотрим параметры измерения `x` растра:
```{r}
attr(img, 'dimensions')[['x']]
```
Наконец, дополнительно к этому атрибут `dimensions` имеет свой собственный атрибут `raster`, который необходим для того чтобы определить какие именно измерения растра являются пространственными, а также установить преобразования, которые будут над ними производиться при анализе или визулизации:
```{r}
img |> attr('dimensions') |> attr('raster') |> str()
```
Видно, что атрибут `raster` содержит 3 элемента:
- `dimensions`: названия измерений, которые являются пространственными
- `affine`: параметры аффинного преобразования, которое будет применяться к пространственным измерениям перед их отображением или применением в операциях пространственного анализа
- `curvilinear`: логическое значение, которое устанавливает, является ли растр криволинейным (в этом случае в параметре `values` пространственных измерений должна быть матрица координат)
### Визуализация {#raster_viz}
#### Статичные карты {#raster_viz_static}
Для визуализации одноканальных растров используется функция `plot()`. В простейшем виде ей достаточно просто передать визуализируемый растр:
```{r}
par(mfrow = c(1,1))
plot(dem)
```
Поскольку растры часто используют в классифицированном виде, вы можете сформировать вектор граничных значений классов, вектор цветов классов, и передать их в параметры `breaks` и `col` функции `plot()` соответственно. Если параметр `breaks` не определять, то весь диапазон значений растра будет разбит на равные интервалы соответственно количеству цветов. Если не определять параметр `col`, то будет применена стандартная палитра `terrain.colors`. Вы также можете использовать одну из готовых палитр цветов или создать ее вручную (см. посвященную графической подсистеме R):
```{r}
brks = c(-12000, -5000, -2500, -1000, -200, 0, 200, 500, 1000, 2000, 4000, 8000)
clrs = c(
"steelblue4",
"steelblue3",
"steelblue2",
"steelblue1",
"lightskyblue1",
"darkseagreen",
"lightgoldenrod1",
"darkgoldenrod1",
"darkorange",
"coral2",
"firebrick3")
plot(dem, breaks = brks, col = clrs)
plot(dem, col = colorRampPalette(c("black", "white"))(255))
plot(dem, col = rainbow(10))
```
Для синтезирования цветного изображения на основе многоканального растра необходимо объект `stars` предварительно подать в функцию `st_rgb()`:
```{r}
plot(st_rgb(img))
```
Поскольку при визуализации космических снимков часто используют различные варианты синтеза каналов (чтобы лучше дешифрировать те или иные категории объектов), функция `st_rgb()` предоставляет такую возможность. Достаточно перечислить последовательность каналов растрового стека (по умолчанию эти каналы будут подставлены в каналы R, G и B соответственно):
```{r}
st_rgb(img[,,,c(1, 2, 3)]) |> plot()
st_rgb(img[,,,c(1, 3, 2)]) |> plot()
st_rgb(img[,,,c(2, 1, 3)]) |> plot()
st_rgb(img[,,,c(2, 3, 1)]) |> plot()
st_rgb(img[,,,c(3, 1, 2)]) |> plot()
st_rgb(img[,,,c(3, 2, 1)]) |> plot()
```
Вы можете совмещать на картах несколько растровых и векторных слоев точно так же как и при совмещении векторных данных (указав параметр `add = TRUE` при вызове функции `plot()`):
```{r}
plot(st_rgb(img), reset = FALSE)
plot(outlines, border = rgb(1,1,1,0.5), lwd = 0.5, add = TRUE)
```
#### Интерактивные карты {#raster_viz_inter}
Объекты типа stars могут быть визуализированы аналогично векторным на интерактивных картах `mapview`:
```{r, eval = F}
mapview(dem, at = brks, col = clrs)
```
```{r, echo = FALSE}
knitr::include_graphics('images/mapview4.png')
```
### Обрезка {#raster_crop}
Одна из распространенных задач при работе с растрами --- это обрезка, то есть удаление растровых данных, находящихся за пределами указанной территории. Чаще всего обрезку делают либо ограничивающим прямоугольником, либо полигональным объектом. Рассмотрим оба варианта:
```{r}
# Обрезка по ограничивающему прямоугольнику
box = st_bbox(c(xmin = -80, xmax = -10, ymax = 85, ymin = 58), crs = st_crs(4326))
dem_greenland = dem[box]
dem_greenland
plot(dem_greenland)
```
Аналогичным образом можено обрезать растр контуром выбранной страны:
```{r}
country = countries |>
filter(name == 'Afghanistan')
dem_country = dem[country]
dem_country
plot(dem_country)
```
### Индексирование
Ортогональная структура объектов типа stars позволяет выполнять по ним различные срезы, отсекая ненужные данные. Для этого используется привычный по работе с векторами оператор квадратной скобки `[`, который работает следующим образом:
- первый аргумент выбирает атрибут
- второй и последующий аргументы выбирают измерения.
Таким образом, при работе с растрами, которые содержат один атрибут, вам необходимо указать 4 индекса: `[var, x, y, band]`, где `var` - это название или порядковый номер атрибута, а `x, y, band` --- порядковые номера двух координатных и одного семантического измерения.
Например:
```{r}
# выбрать 1 канал
ch1 = img[,,,1]
ch1
plot(ch1)
# выбрать диапазон ячеек растра
frag = img[, 320:470, 100:255, ]
frag
plot(st_rgb(frag))
```
### Манипуляции
Объекты типа `stars` поддерживают манипуляции, аналогичные тем, что могут применяться к векторным данным. Посмотрим это на примере данных по высоте земной поверхности с учетом и без покровного оледенения:
```{r}
etopo = read_stars(c('data/etopo1_bed.tif', 'data/etopo1_ice.tif'))
etopo
```
Для начала переименуем переменные:
```{r}
etopo = etopo |> setNames(c('bed', 'ice'))
etopo
```
После этого посчитаем, например, толщину покровного оледеления как разность `ice` и `bed` через *мутирование*:
```{r}
etopo = etopo |>
mutate(depth = ice - bed)
plot(etopo['depth'],
col = cm.colors(5),
breaks = c(0, 500, 1000, 2000, 3000, 4000),
main = 'Мощность покровного оледенения',
reset = FALSE)
plot(oceans, col = 'steelblue', add = TRUE)
```