fleur.js

Оценка кормовых угодий на JavaScript

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

Представим, что вы заимели в распоряжение некоторую площадь земли и намереваетесь распорядиться ей по хозяйски. Решив финансовые, кадастровые и прочие вопросы вы неизбежно придете к вопросу: «Какова земля по своим качествам?». Годится ли для посадки помидоров или кроме кривой сосны ничего не вырастет? Какой цемент выбрать для фундамента: исходя из сухой почвы или периодически подтопляемой? Почему у соседа вызревает полна жопа огурцов, а у вас дохнет последний подорожник? Потому, что в почве элементов не хватает или соседские коровы все вытоптали?

Когда участок мал, ответ познают органолептическим методом. Но что делать, если вам нужны точные результаты? Например, ваша сестра вышла замуж за премьер-министра и вы завладели миллионами гектаров угодий. Первая мысль — отобрать пробы почв из разных мест и отдать в физико-химическую лабораторию. Идея хороша, но есть три «но». Во-первых, это будет стоить безумных денег. Во-вторых, физико-химические свойства почвы постоянно меняются. Прошел дождь — и вот вам иное соотношение растворимых солей. Выглянуло солнце — изменилась влажность. В третьих, и это самое главное, вам необходимо знать не абсолютные концентрации микроэлементов, а то, насколько успешно они поглощаются растениями.

Логично оценить угодья по местным растениям. Если условные редька и одуванчик нуждаются в одинаковых условиях, значит поле одуванчиков подходит для редьки. Это примитивная, но верная мысль. Преимущество растений в длительном росте, который накапливает свойства территории за большой период. Кроме того, изучая растительность мы снижаем риск ошибки, связанной с бочкой Либиха.
Бочка Либиха

Бочка Либиха — принцип названный по фамилии немецкого профессора. В скучной экологической литературе он чаще упоминается как закон лимитирующего фактора. Наполним водой деревянную бочку, которую сколотили из досок разного размера. По заполнению, вода начнет вытекать через самую короткую доску. Наша редька будет дохнуть именно от самого проблемного элемента. Мы проверили все: азот, фосфор, калий, серу, железо и кучу других элементов — все в порядке. Но случайно забыли про марганец и вот наша условная редька уже в точечных пятнах хлороза тщетно пытается синтезировать аскорбиновую кислоту, дожидаясь малейшего повода для смерти. Условный одуванчик реагирует на всю совокупность физико-химических условий произрастания. Если он бодр и весел, за редьку можно не переживать.

Жизнь устроена сложнее наших условностей. Не бывает двух организмов, а уж тем более видов с одинаковыми требованиями к условиям обитания. «Что русскому хорошо, то немцу смерть» в переводе на экологический язык называется нормой реакции и выражается в кривой жизнедеятельности:
Кривая жизнедеятельности

Принцип влияния экологических факторов на организм выражается пословицей «Все хорошо в меру». Задача — сравнить между собой «меры» различных видов и применить к ним школьный принцип «меньше большего, больше меньшего». Если мы нашли одуванчик, значит условия жизни для одуванчика подходят. Если рядом с одуванчиком сныть, значит условия жизни подходят для одуванчика и сныти одновременно. Если мы собрали тридцать разных видов, значит условия подходят одновременно для каждого из них. Чем больше видов, тем уже диапазон факторов произрастания:
Сужение диапазона факторов произрастания

Теоретически, мы можем построить такие кривые для любого фактора окружающей среды (вопрос эмергентности опустим — это тема долгого и сложного разговора). Нас не волнует медианное значение влажности почв. Мы хотим знать, достаточно ли влаги растениям? Это не одно и тоже: весной воды хоть залейся, но растения живут в условиях физиологической сухости, поскольку не могут впитать воду из холодной почвы. Вопрос шкалирования («в каких единицах измерять») решается принципом канторово-пелевинской «сиськой в себе». Рисуем пустую стобалльную шкалу, после идем в самое сухое место, определяем найденные растения и вписываем их в левую часть шкалы. Потом идем в самое сырое место и вписываем местные растения в правую часть шкалы. После делаем несколько десятков тысяч описаний из разных мест и расставляем на шкале встреченные виды.

В одну из ночей опустите луч фонарика вертикально вниз. На землю ляжет тень от травы — проекция растений на плоскость. Если забыть, что луч бьет из одной точки или взять громадный прожектор, то площадь тени будет пропорциональна густоте растений. В геоботанике этот показатель называется проективным покрытием. Глазомерно он вычисляется как доля покрытой растениями территории. Сумма проективных покрытий всех видов больше общего покрытия травостоя, поскольку разные виды перекрывают друг друга. Псевдоматематики называют проективное покрытие вероятностью обнаружения вида в точке со случайными координатами или говорят о других диких концепциях, но на практике без инструментов никто не способен оценить густоту растений точнее 5-10 процентов (хоть все говорят, что могут), поэтому описание дополняют словами «единично», «незначительно» и прочей гуманитарной фигней.

Идя по градиенту влажности от сырого к сухому месту, вы встретите новые виды. Пока еще чахлые и редкие. Они едва выживают при такой влажности. Скоро этих растений станет больше. В идеальных условиях проективное покрытие возрастет до ста процентов — вспомните непроходимые заросли крапивы urtica dioica. На подходе к сухому месту проективное покрытие уменьшается, в сухих условиях остаются лишь единичные растения. В очень сухих ваши они уступают другим видам. За время похода вы пройдете несколько куполообразных изменений проективного покрытия, которые вспомните составляя шкалу:
Градиент изменения условий среды

Когда первая шкала готова, делим весь массив описаний на группы по влажности территорий и для каждой группы тем же методом строим шкалу «бедность-богатство-засоленность». Затем итеративно повторяем процесс для переменности увлаженения, аллювиальности почв, пастбищной дегрессии (вытоптанности) и чего душа пожелает.

Для работы необходимы десятки лет, миллиарды рублей и армия ботаников. Сегодня такие ресурсы получить невозможно, но по счастью кровавый сталинизм оставил в наследство не только сопливый дудевский фильм, но и результат работы института луговой и болотной культуры (сейчас НИИ кормов имени Вильямса), где под руковоством Л.Г. Раменского подготовлена прекрасная монография «Экологическая оценка кормовых угодий по растительному покрову». Книга содержит короткую пояснительную записку, методы анализа и таблицу на сотни страниц, где указано размещение видов растений на экологических шкалах в зависимости от проективного покрытия.
Книга Экологическая оценка кормовых угодий по растительному покрову

Свыше полувека работа с этой книгой выглядит так: геоботаник описывает проективные покрытия видов на площадке, возвращается домой, достает миллиметровку и рисует на ней шкалу влажности (сто двадцать единиц). Смотрит на значение проективного покрытия первого вида, находит этот вид где-нибудь на триста седьмой странице и откладывает на миллиметровке указанный в книге диапазон. Потом второй вид, потом третий и так до конца. Вид а: от сорока до пятидесяти, вид б: от сорока пяти до семидесяти, вид в: от двадцати до сорока восьми. На основе «больше меньшего, меньше большего» оцениваем увлажнение участка от сорока пяти до сорока восьми баллов. Потом переходим к вычислению богатства почвы, потом к остальным показателям. Спустя несколько часов беремся за другое описание.

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

Казалось бы, любой первокурсник-технарь напишет алгоритм за пару часов, любой школьник, отличающий инкремент от компиляции закодит его за вечер. Все просто как две копейки. Но все программные реализации (включая мою работу десятилетней давности) напоминали сплетенные из вареных макарон костыли для безруких. Потому что легче «Анну Каренину» на машинный язык перевести, чем автоматизировать работу с экологическими шкалами Раменского.

Проблема исключительно гуманитарная. Ботаники — от студентов до докторов наук до сих пор не отличают электронную информацию от цифровой. Наука о растительности — это пещера в котором обитает карго-культ технологического развития. Попросите любого выслать метаданные описаний — столкнетесь с непониманием. Договоритесь о данных в цифровом виде — получите на почту вордовский файл с таблицами. Гусиные перья сменила печатная машинка, печатную машинку компьютер, но сама технология изучения растительности осталась на уровне гусиных перьев.

Геоботаническое описание обычно содержит в себе метаданные (где, кем, когда и др.), описание древостоя (при наличии оного и отсутствии отдельных таксационных работ), подроста, подлеска и таблицы проективных покрытий травяно-кустарничкового и мохово-лишайникового ярусов. Камеральная обработка сводится к переносу данных в эксель, часто в том же виде, в каком они представлены на бланке. Форма бланков у всех разная, поэтому данные разных авторов не сравнимы без мучительной корректорской работы. Я опускаю разность методик, разность понимания видов, здесь разговор только о технической стороне вопроса.
Образец геоботанического описания

Без общепринятого формата, любой код автоматизации придется переписывать под каждого автора. Но это не спасет без решения проблемы субъективных оценок. Нельзя вместо оценки проективного покрытия скормить алгоритму понятия «единично», «изредка», «две куртины» и прочий бред (все из реальных описаний). Предположим, мы исключим такие данные из выборки. Если речь об экологическом шкалировании, то это допустимо. Но следом возникает проблема таксономии.

Линней, работая с номенклатурой не думал о том, что латынь уйдет в прошлое, а коробка размером с небольшой саквояж уместит в себе всю ботаническую литературу. Сегодня виды сохраняют латинское название (и это правильно), но саму латынь никто не помнит, герундий от герундива не отличает, рода путают между собой. В результате окончания видов обычно записаны с ошибками. Другое проблемное место — нечитаемые буквы. Попробуйте спустя месяц по памяти верно воспроизвести krascheninnikovii, krascheninnikoviana, или krascheninnikoviorum. Тут ботаники с лицом честного гаишника воскликнут, что они, дескать все выверяют по справочнику Черепанова. Клевер луговой у них трифолиум пратенсе, а клевер ползучий — амория репенс. Не верьте. При мне за несколько лет луговик извилистый из дешампсии стал лерхенфельдией, а из последней превратился в авенеллу. Все обсуждают подобные мелочные вопросы и никто не ничего хочет менять всерьез. А без изменений весь накопленный материал стоит дешевле макулатуры.

Я давно не работаю в государственном институте. Пол-месяца ввода, пол-месяца обработки и месяц дальнейшей психотерапии в мой прайс не включен, поэтому пришлось уйти от ботанических практик и минуя табличные редакторы, вводить данные сразу в виде js-объекта (в данные внесены искажения по условиям контракта, комментарии добавил для наглядности):

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
var descript = [
{
time:20160602,
note:'GR-0602-1',
tags:'Сосняк, Мяглово-Карьер',
lat:59.82739,
lng:30.69896,
datum:'4326',
author:'S.N.Golubev',
feedback:'schwejk-rpnt@rambler.ru',
license:'CC-BY-NC-SA-3.0',
source:'fieldobserve',
aream:2411,
dendro:{   /*Характеристики древостоя*/
	allvolumemcb:329,   /*Запас, куб. м*/
	allfullmsq:34.4,    /*Абсолютная полнота, кв. м*/
	pins__sylrs:{       /*Данные по сосне - pinus sylvestris*/
		volumemcb:329,   /*Запас, куб. м*/
		fullmsq:34.4,    /*Абсолютная полнота, кв. м*/
		diasm:23,        /*Средний диаметр, cм*/
		heightm:24.7,    /*Высота, м*/
		age:70,          /*Возраст, лет*/
	},
},
grass:{   /*Данные по живому напочвенному покрову*/
	allcover:50,   /*Общее проективное покрытие яруса*/
	cover:{        /*Повидовое проективное покрытие*/
		vacnm_myrls/*Черника - Vaccinium_myrtillus_L*/:20,
		vacnm_vitd/*Брусника - Vaccinium_vitisidaea_L*/:30,
		conlr_majls/*Ландыш - Convallaria_majalis_L*/:5,
		trils_eurp_/*Седмичник - Trientalis_europaea_L*/:0.1,
		desps_flexs/*Луговик - Deschampsia_flexuosa_Trin*/:10,
		melrm_prans/*Марьянник - Melampyrum_pratense_L*/:0.1,
		luzl__pils_/*Ожика - Luzula_pilosa_L_Willd*/:0.1,
		calln_vulrs/*Вереск - Calluna_vulgaris_L_Hull*/:0.1,
		charn_anglm/*Кипрей - Chamerion_angustifolium_L_Holub*/:0.1,
		fragr_vesc_/*Земляника - Fragaria_vesca_L*/:0.1,
		soldg_virgr/*Золотарник - Solidago_virgaurea_L*/:0.1,
		maimm_biflm/*Майник - Maianthemum_bifolium_L_FW_Schmidt*/:0.1,
		desps_cests/*Щучка - Deschampsia_cespitosa_L_Beauv*/:0.1,
		},
	},
undergrass:{/*Данные по мохово-лишайниковому ярусу*/
	allcover:40/*Общее проективное покрытие яруса*/,
	cover:{
		polhm_specs:0.1/*Политрихум*/,
		plezm_schbr:40/*Плеуроциум*/,
		},
	},
},
]

Структура данных повторяет бланк описания (метаданные-древостой-живой напочвенный покров-мохово-лишайниковый ярус). Видам с незначительным обилием присвоено проективное покрытие 0.1%. Видовые названия записаны в виде одиннадцати символов: пять на род, пять на вид и символ нижнего подчеркивания между ними. Род и вид преобразуются в код вида по такому принципу:
— Первые три буквы таксона берутся без изменений (Convallaria — con);
— Последние две соответствуют двум последним согласным таксона (Convallaria — lr);
— Если букв в таксоне меньше пяти, пропуски заполняются нижним подчеркиванием (Poa pratense — poa___prans);
— Если после первых трех букв одна согласная или согласных нет — пустые места заполняются нижним подчеркиванием (Luzula_pilosa — luzl__pils_).

Это не самый удачный принцип, поскольку требует исключений. Например, одуванчики Taraxacum laticordatum и Taraxacum latisectum кодируются одинаково: tarcm_lattm. К более простому решению, которое обеспечивает автоматическую кодировку списка таксонов я пока не пришел. К счастью исключения редки даже для региональной флоры, для локальной совсем незначительны и легко отлавливаются простой проверкой по сортированному массиву.

После я перевел таблицу из книги Л. Г. Раменского в js-массив следующего вида:

1
2
3
4
5
6
7
8
9
10
11
12
var ramen = [
["КОД", "ВИД", "ШКАЛА", "ЗОНА", "ПОЧВА", "ПОКРЫТИЕ", "MIN", "MAX"],
["acalm_punns", "Acanthophyllum pungens (Bunge) Boiss.", "water", false, false, 0.3, 10, 15],
["acalm_punns", "Acanthophyllum pungens (Bunge) Boiss.", "water", false, false, 0.1, 8, 1000],
["acalm_punns", "Acanthophyllum pungens (Bunge) Boiss.", "rich", false, false, 0.3, 12, 15],
["acapr_schhr", "Acarospora schleicheri (Ach.). Mass.", "water", false, false, 2.5, 15, 19],
["acapr_schhr", "Acarospora schleicheri (Ach.). Mass.", "water", false, false, 0.3, 11, 22],
["acapr_schhr", "Acarospora schleicheri (Ach.). Mass.", "water", false, false, 0.1, 10, 35],
["acer__plads", "Acer platanoides L.", "water", false, false, 0.1, 65, 71],
["acer__plads", "Acer platanoides L.", "water", false, false, 0, 0, 91],
...
]

Массив состоит из 11 673 элементов, включая заголовок. Каждый элемент содержит информацию о видовом коде, таксоне, экологической шкале, минимальном и максимальном балле шкалы. Информация о типе почв и природно-климатической зоне отсутствует, но на случай развития проекта для этих данных оставлено место. В тех случаях, когда минимальный балл в книге не указан, в таблице стоит 0. Если не указан максимальный балл, в таблице стоит 1000.

Скрипт расчета Fleur.js содержит всего полторы сотни строк, но его следует сократить вдвое, поскольку вторая функция на 99% дублирует первую. На момент написания я вконец обленился и просто скопипастил свою же функцию, дополнив ее несколькими строками. Функция «ramenall(e)» подхватывает первое описание в серии, переводит абсолютные значения проективного покрытия из геоботанического описания в группы проективных покрытий шкал Л. Г. Раменского (единично-0.1, 0.1-0.3, 0.3-2.5, 2.5-8, 8 и более процентов). После сравнивает видовые списки из описания и таблицы экологических шкал на основе общего ключа кода видов. Найдя совпадение в коде, функция заполняет массив номером и таксонами описания с присвоением минимального и максимального балла для каждого вида. Если для вида информация отсутствует, скрипт выдает «-Infinity, Infinity;». После программа переходит к следующему описанию из серии. Когда описания заканчиваются, программа выводит собранный массив на html-страницу.

Функция «ramenbase(e)» выполняет те же самые операции, только для каждого описания в серии формирует массив с минимальными и максимальными значениями баллов. Из массива минимальных баллов отбирает наибольший, из массива максимальных — наименьший. Итогом выпадает таблица с номером описания, минимальным и максимальным значением на экологической шкале.
Больше меньшего, меньше большего

Обе функции потребляют на вход одинаковые аргументы: «rich» — богатство и засоленность почвы, «water» — влажность почвы, «waterwave» — переменность увлажнения, «alluvium» — аллювиальность почвы и «degrade» — пастбищная дегрессия.

Качество кода оставляет желать лучшего, но поскольку он написан три года назад по дороге из Кингисеппа в деревню Лисино-Корпус Ленинградской области, я доволен и без нужды ничего менять не планирую.

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
// Полный расчет (значения для всех видов)
function ramenall(e){
 
  for(var a=0; a<descript.length; a++)
  {
	  var gbo = descript[a]; // Текущее описание в обработке
	  var spec=[];           // Вид
	  var pokr=[];           // Проективное покрытие в процентах
	  var pokrball=[];       // Балл покрытия по Раменскому
	  var spectable=[];      // Обертка для spec, pokr, pokrball
 
// Перевод % покрытия в % покрытия по Раменскому	  
	  for(var key in gbo.grass.cover)
	  {
		  spec.push(key);
		  pokr.push(gbo.grass.cover[key]);
		  if(gbo.grass.cover[key]>=8.0 &&
				gbo.grass.cover[key]<100){pokrball.push(8.0);}
		  if(gbo.grass.cover[key]>=2.5 &&
				gbo.grass.cover[key]<8.0){pokrball.push(2.5);}
		  if(gbo.grass.cover[key]>=0.3 &&
				gbo.grass.cover[key]<2.5){pokrball.push(0.3);}
		  if(gbo.grass.cover[key]>=0.1 &&
				gbo.grass.cover[key]<0.3){pokrball.push(0.1);}
		  if(gbo.grass.cover[key]>=0.0 &&
				gbo.grass.cover[key]<0.1){pokrball.push(0.0);}
		}
 
// Заполнение таблицы для сравнения со шкалами    
	  spectable.push(spec);
	  spectable.push(pokr);
	  spectable.push(pokrball);
 
// Сравнение со шкалами   
	  for(var i=0; i<spec.length; i++)
	  {
		  for(var k=0; k<ramen.length; k++)
		  {
			  if(spectable[0][i]==ramen[k][0] && //Код вида
				ramen[k][2]==e && // Шкала (указана в HTML)
				ramen[k][3]==false && // Природная зона (игнорируется)
				ramen[k][4]==false && // Тип почвы (игнорируется)
				ramen[k][5]==spectable[2][i] // Проективное покрытие
				)
				{
// Публикация отчета в HTML
				var str = document.getElementById('tableResult');
				var add = str.insertRow(-1);
				var addTr = document.createElement("tr");
				var addTd = document.createElement("td");
					addTd.innerHTML=descript[a].note+", ";
					addTr.appendChild(addTd); // Номер описания
				var addTd = document.createElement("td");
					addTd.innerHTML=ramen[k][1]+", ";
					addTr.appendChild(addTd); // Название вида
				var addTd = document.createElement("td");
					addTd.innerHTML=spectable[1][i]+"%,      ";
					addTr.appendChild(addTd); // Покрытие
				var addTd = document.createElement("td");
					addTd.innerHTML=ramen[k][6]+",      ";
					addTr.appendChild(addTd); // Максимум
				var addTd = document.createElement("td");
					addTd.innerHTML=ramen[k][7];
					addTr.appendChild(addTd); // Максимум
				str.appendChild(addTr);
				};
		};
	};
};
}
 
// Краткий расчет (классический, результаты для пробной площади в целом)
function ramenbase(e){
	for(var a=0; a<descript.length; a++)
	{
		var gbo = descript[a];
		var spec=[];
		var pokr=[];
		var pokrball=[];
		var spectable=[];
 
		for(var key in gbo.grass.cover)
		{
			spec.push(key);
			pokr.push(gbo.grass.cover[key]);
			if(gbo.grass.cover[key]>=8.0 &&
				gbo.grass.cover[key]<100){pokrball.push(8.0);}
			if(gbo.grass.cover[key]>=2.5 &&
				gbo.grass.cover[key]<8.0){pokrball.push(2.5);}
			if(gbo.grass.cover[key]>=0.3 &&
				gbo.grass.cover[key]<2.5){pokrball.push(0.3);}
			if(gbo.grass.cover[key]>=0.1 &&
				gbo.grass.cover[key]<0.3){pokrball.push(0.1);}
			if(gbo.grass.cover[key]>=0.0 &&
				gbo.grass.cover[key]<0.1){pokrball.push(0.0);}
		}
 
		spectable.push(spec);
		spectable.push(pokr);
		spectable.push(pokrball);
 
		var ecoscalemin=[];// Шкала минимумов
		var ecoscalemax=[];// Шкала максимумов
 
		for(var i=0; i<spec.length; i++)
		{
			for(var k=0; k<ramen.length; k++)
			{
				if(spectable[0][i]==ramen[k][0] &&
				ramen[k][2]==e &&
				ramen[k][3]==false &&
				ramen[k][4]==false &&
				ramen[k][5]==spectable[2][i]
				)
				{
					ecoscalemin.push(ramen[k][6]);
					ecoscalemax.push(ramen[k][7]);
				};
			};
		};
 
		var str = document.getElementById('tableResultKratk');
		var add = str.insertRow(-1);
		var addTr = document.createElement("tr");
		var addTd = document.createElement("td");
			addTd.innerHTML=descript[a].note+",  ";
			addTr.appendChild(addTd); // Номер описания
		var addTd = document.createElement("td");
 
			// Максимальное значение шкалы минимумов
			addTd.innerHTML=Math.max.apply(Math, ecoscalemin)+",  ";
			addTr.appendChild(addTd); // Минимум
		var addTd = document.createElement("td");
 
			// Минимальное значение шкалы максимумов
			addTd.innerHTML=Math.min.apply(Math, ecoscalemax)+";  ";
			addTr.appendChild(addTd); // Максимум
		str.appendChild(addTr);
	};
}

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

Финализировать эту эпопею нужно тремя вопросами: почему JavaScript?, что дальше? и как использовать полученные результаты анализа?. JavaScript — потому что эти расчеты иногда требуется выполнять на чужих компьютерах без установленного R, Wine или другого софта. Что дальше — не знаю. Есть пару идей, но я три года ничего не менял, могу еще три года ничего не менять. А как использовать результаты я не расскажу, поскольку строки этой статьи все-равно никто не увидит. Программисты бросят читать на втором слове, а ботаники на четвертом.


По адресу городшахты.рф/source/fleur/ лежит готовая к использованию программа. Можете указать ссылку на свой набор геоботанических описаний в указанном выше формате и рассчитать богатство, увлажнение, переменность водного режима, аллювиальность и пастбищную дегрессию почв.
Полноценное теоретическое обоснование, альтернативные методы и материалы для контроля доступны в книге: Л. Г. Раменский, И. А. Цаценкин, О. Н. Чижиков, Н. А. Антипин «Экологическая оценка кормовых угодий по растительному покрову» Всесоз. науч. -исслед. ин-т кормов им. В. Р. Вильямса. М. : Сельхозгиз , 1956 470, [2] с.: ил., 1 л. граф.

Визуализация рельефа по данным SRTM и ASTER GDEM в QGis+SAGA

Интро. В настоящей статье специально рассмотрен случай отображения рельефа для значительной территории по неоднородным данным о рельефе. В связи с этим, при работе с более простыми территориями, алгоритм может быть упрощен. Под визуализацией рельефа будем понимать создание растра отмывки и шейп-файла изолиний с атрибутивными данными о значении каждой изолинии.

В качестве показательной территории взяты Соликамский и Красновишерский районы Пермского края. В качестве подложки карта OpenStreetMap Mapnik Standart:
Соликамск и Красновишерск

Инструментом для работы послужит QGis 2.18.4 с подключенными алгоритмами SAGA. Все операции, связанные с фильтрацией и созданием изолиний можно выполнять как внутри QGis, так и запуская гис SAGA в качестве автономного приложения.

Рельеф на средне- и крупномасштабных картах в настоящее время в большинстве случаев отображается с помощью данных SRTM или ASTER GDEM, что связано с их глобальным охватом, открытостью и простотой получения. Разрешение этих данных (SRTM 90 м/пикс, ASTER GDEM 30 м/пикс) позволяет, при должной обработке, показывать особенности рельефа примерно до 15 зума. Несмотря на то, что данные ASTER точнее, их использование затруднено необходимостью дополнительной фильтрации для отсеивания значений, не отражающих реальный рельеф (например, высоты леса и жилой застройки). Оптимальных алгоритмов для такой процедуры, которые дают стабильный результат для значительной территории, не разработано, в результате чего, образец визуализации менее точных данных SRTM оказывается обычно более качественным как с геодезической, так и с художественной точек зрения. Однако, севернее 60° с.ш. и южнее 54° ю.ш. данные SRTM отсутствуют, что вынуждает в конечном итоге использовать оба набора данных при визуализации рельефа на территориях, выходящих за границы покрытия SRTM.

Наш случай именно такой (снизу данные SRTM, сверху ASTER GDEM):
ASTER и SRTM

Данные SRTM доступны из различных источников, из которых наиболее удобны сайты cgiar, gis-lab и viewfinderpanoramas. Я предпочитаю использовать последний, поскольку многие сцены там объединены и загружены сразу в geotiff-растры (обычно SRTM представлен в hdt-формате).

ASTER получить немного сложнее: сайты геологической службы США и NASA позволяют скачивать различные данные ДЗЗ, что требует от пользователя определенной подготовки. Кроме того, эти сайты иногда бывают недоступны, либо работают чрезвычайно медленно. В этих случаях можно скачать всю базу через торрент. Дополнительные источники получения данных SRTM и ASTER доступны на странице получения данных.

Помимо растровых данных, для работы нам потребуется шейп-слой с границами районов, который можно скачать с сайта gis-lab или загрузить с помощью overpass.

После того как исходные получены, можно запускать QGis:
ASTER и SRTM в QGis

Для начала объединим сцены ASTER в единый растр с помощью меню «растр-прочее-объединение»:
1объединение растров

В диалоговом окне укажем директорию со сценами и название итогового файла.
2объединение растров

Обратите внимание, что в некоторых версиях GDAL отказывается работать, если пути к файлам содержат кириллические символы. В моем случае все прошло успешно:
3объединение растров

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

Выделяем в ТОСе наш ASTER, и через правую кнопку мыши вызываем диалоговое окно сохранения растра:
Сохранение растра в QGIS

Здесь обратите внимание на то, что-бы растр сохранялся как данные. Разрешение уменьшаем в три раза, т.е. вместо 18001 столбца вписываем 6000, а вместо 7201 строк вписываем 2400:
Сохранение растра в QGIS2

После сохранения растра он выглядит загрубленным, но все-равно более информативным, чем SRTM:
Сохранение растра в QGIS3

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

По той-же схеме объединим полученный растр с растром SRTM:
DEM-композит

Получившийся растр охватывает излишне большую территорию, что будет отнимать у нас лишнее время на его обработку. Что-бы избежать этого, отрежем все, что не входит в область наших интересов, указав при сохранении растра видимый охват (не забывайте сохранять растр как данные!):

image

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

Откроем панель инструментов в меню «анализ данных» и в списке геоалгоритмов SAGA-Grid-Filter выберем алгоритм «Простой фильтр»:

После нескольких минут обработки, алгоритм выдаст сглаженный растр:
отфильтрованный растр

Его мы и будем использовать для отмывки. С помощью меню «растр-морфометрический анализ-теневой рельеф»:

вызовем диалог создания карты теней:

На этом этапе следует знать одно неявное правило. В том случае, когда вы планируете использовать теневую отмывку в качестве подложки саму по себе, можно использовать значения по умолчанию. В том случае, когда ваша отмывка ложится на некую базовую карту (в нашем случае, такой картой служит OpenStreetMap), следует повернуть источник освещения на сто восемьдесят градусов. Дело в том, что стандартная отмывка представляет собой темный растр, который при наложении не только перестает читаться, но и зашумливает перекрываемые слои. Для того, что-бы это избежать, отмывку следует инвертировать, но в этом случае, горы выглядят как впадины, а каньоны напоминают холмы. Учитывая это, мы заранее изменяем источник освещения, что позволит нам при инвертировании сохранить визуальную форму отмывки. По умолчанию, источник освещения расположен в районе трехсот градусов, чего, конечно-же в природе почти никогда не бывает. Еще Салищев указывал на эту особенность — привычная отмывка рельефа обязана своему появлению лампам, которые обычно устанавливали слева от кульмана. Мы поменяем значение «300» на «120» и через несколько секунд алгоритм выдаст нам вот такой результат:

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

Через меню «растр-извлечение-обрезка»

вызовем диалог, в котором укажем исходный и результирующий растр и шейп-маску по которой будет произведена обрезка:

В результате получим вот такую картину:

Двойным кликом по полученному растру в ТОСе вызовем меню свойств, где сменим градиент с «от белого к черному» на «от черного к белому». После применения изменений растр инвертируется. В месте сочленения данных ASTER GDEM и SRTM осталась небольшая белая полоса, однако, после того как будет установлена прозрачность, а сама отмывка наложена на подложку, заметить эту полосу будет практически невозможно:

Для того, что-бы не инвертировать растр при каждой новой загрузке, сохраним его как новый слой, но на этот раз в меню сохранения отметим его не как «данные», а как «изображение». На этом создание отмывки закончено. Установим прозрачность отмывки 95% и подложим под нее OpenStreetMap:

Так выглядит чистая карта OSM:

А так выглядит карта OSM с наложенной на нее картой теней в районе соприкосновения данных SRTM и ASTER:

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

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

С практической точки зрения эта проблема решается так. Создадим временный полигональный слой:
Новый временный слой2

Сделаем его редактируемым (иконка желтого карандаш), после чего установим режим добавления нового объекта
Редактирование в QGis

и обведем один из районов:

Сохраним фрагмент растра для этого участка (не фильтрованного растра, а исходного, мы же усложняем себе задачу!), обрежем его по обведенной области и отфильтруем с теми же параметрами, что и при создании отмывки. Затем с помощью геоалгоритма SAGA-Shapes-GRID-Горизонтали по ЦМР создадим изолинии через каждые 50 метров высоты.

Фильтрация не только убирает излишний шум, упрощая и выравнивая изолинии, но и позволяет «сцепить» наши разнородные данные. Вот пример извлечения изолиний из сырого растра:

Отчетливо просматривается линия сочетания данных ASTER и SRTM. При различных способах фильтрации растра ASTER GDEM эту линию можно делать более или менее заметной о чем я упоминал в начале данной статьи.

Изолинии из отфильтрованного растра на этот район выглядят так:

На границе растра изолинии замыкаются и не несут в себе географического смысла. Такие участки в последующем будут удалены. Именно поэтому обрезка dem-растра производилась нами не по границе района а по внешнему слою.

Аналогичные операции повторяем для второго района. Обратите внимание, что полигоны обрезки растра перекрывают друг друга:

Чем больше полигоны обрезки растров, тем дольше времени будет затрачено на обработку, но тем точнее будут соединяться изолинии:

После того, как изолинии извлечены, остается только обрезать их по контуру границ, сохранив оригинальные значения атрибутов изолиний. Для этого успешно применяется алгоритм «SAGA-Shapes-Lines-Пересечение линий и полигонов»:

Небольшая настройка стиля и изолинии готовы:

Обычная карта OpenStreetMap:

Тот же фрагмент карты, но с наложенной картой теней и горизонталями:

Отдельно необходимо вынести проблему неоднородности данных ASTER GDEM по качеству. Даже на нашем примере видно, насколько сильно артефакты отсутствующей и ошибочной информации сказываются на качестве визуализации рельефа в целом:

Данная проблема не имеет однозначного механистического решения. Наиболее оптимальные способы ее устранения зависят от требований к визуализации, выбранного региона и доступной вычислительной мощности. В качестве одного из способов решения я предлагаю использовать последовательное применение фильтра DTM (предельный уклон местности 10 градусов, радиус поиска 2 пикселя), заполнение пропусков в образованном в результате DTM-фильтрации растра (порог напряжения 10) и последующая фильтрация простым фильтром (круговой режим поиска, гладкий фильтр, радиус 5px). Этот метод не позволяет полностью избавиться от артефактов, но существенно снижает их число и сглаживает, что определенно положительно сказывается на визуализации рельефа:

Карта OpenStreetMap без отмывки и изолиний:

Карта OpenStreetMap с отмывкой и изолиниями:

Метод Бенфорда в оценке достоверности данных

Метод Бенфорда в оценке достоверности данных

Друзья мои! Вы несомненно знаете больше меня о последних мировых новостях и потому разобщены и тревожны. Но сегодня, у вас будет повод отвлечься. В этот день мы все объединены единым горем утраты. Утрачена флешка, на которой я хранил для вас статью о диссипативной динамике живого напочвенного покрова. Вместе с ней пропало содержимое подарочной бутылки коньяка, мой рукописный реферат на тему «Сатанизм-как социальное явление» и весь тираж осеннего номера «Лабораторного Журнала», отпечатанный в объеме двух с половиной экземпляров. Воистину, в этот день можно посыпать голову пеплом, ибо об этот реферат я в свое время исписал четыре ручки и мне он чертовски дорог, как память о студенческих годах.

Дабы загладить боль утраты, я предлагаю вам статью из пропавшего «Лабораторного Журнала» (а где вы ее теперь прочитаете?), описывающую сущность, принципы применимости и алгоритм метода Бенфорда на примере анализа данных о площадях ООПТ России и площадях, охваченных лесными пожарами в 2009-2013 годах. Сам же я отправляюсь в келью, где буду страдать вплоть до открытия магазина.

Итак, речь пойдет об одном из статистических методах фрактального анализа — оценке бенфорд-последовательности данных. Метод довольно грубый, но в то же время чрезвычайно простой и красивый. С его помощью вы сможете проверить истинность данных, подчиненных экспоненциальному распределению.

Свое название бенфорд-последовательность получила в честь Фрэнка Бенфорда Альберта-младшего — американского инженера-электрика, физика и оптика, жившего в штатах в первой половине XX века. Однако, сам «Закон Бенфорда», он же «закон первой цифры» впервые описан за три года до его рождения американским астрономом, математиком и экономистом Саймоном Ньюкомбом. Работая в 1881 году с логарифмическими таблицами в книгах, он обнаружил, что сильнее всего истрепаны страницы на которых содержаться логарифмы чисел, начинающиеся с единицы. На первый взгляд, вероятность оказаться на первом месте в числе одинакова для всех цифр и составляет 1/9. Однако, чем выше по значению было число, состоящее из первой цифры логарифма, тем в большей сохранности находились страницы. Все это наводило на подозрение о неравномерной встречаемости первых цифр в числах.

Спустя пол-века за эту проблему взялся Фрэнк Бенфорд. Он рассчитал вероятности встречаемости цифр на первом месте в числе для различных данных. Бенфорд использовал площади бассейна 335 рек, удельную теплоемкость материалов, население городов, молекулярную массу химических соединений, номера домов и другие данные. Во всех случаях наблюдалась единая закономерность — чисел, начинающихся на единицу было примерно в шесть раз больше, чем чисел, начинающихся на девятку.  Собранная статистика позволила вывести формулу распределения вероятности появления первой цифры в числе:

P(d) = logb(d+1)-logb(d) = logb(1+1/d)

где:
b — основание системы счисления, в нашем случае b = 10;
d — первая цифра в числе;

На основе этой формулы была построена бенфорд-последовательность — последовательность вероятности появления различных цифр на первом месте числа. Рассчитанная по формуле, эта последовательность выглядит следующим образом: 30.1, 17.6, 12.5, 9.7, 7.9, 6.7, 5.8, 5.1, 4.6. Вероятность того, что на первом месте в числе окажется единица составляет 30.1%, двойка — 17,6% и так далее до девятки (4.6%).

Долгое время, эта интересная закономерность не находила никакого применения. Однако после 1997 года на нее обратили внимание и стали все активнее использовать для проверки фальсификации данных, например результатов голосования (в том числе и в России). В 1997 году М. Нигрини и Л. Миттермайер в издании «Аудит: Журнал теории и практики» опубликовали шесть разработанных математических тестов, основанных на законе Бенфорда. Тесты были успешно введены в практику аудиторской компанией «Эрнст и Янг» и позволили выявить несоответствие между реальными и заявленными данными клиентов.

Необходимо учитывать, что метод Бенфорда применим не ко всем данным. Он выдает значительные погрешности при работе с выборками для которых заданы максимальные или минимальные значения, с выборками, охватывающими только один или два порядка величин и с малыми по объему выборками.

При решении вопроса применимости метода Бенфорда обычно рекомендуют исходить из «естественности» данных (если данные получены в ходе естественного течения событий, то к ним применим метод Бенфорда). Этот критерий верен, но довольно сложен для использования. В ходе работ с бенфорд-последовательностями я пришел к выводу, что метод бенфорда работает только с данными, топологическое множество которых самоподобно, а элементы могут принимать произвольные значения.

Для проверки применимости метода необходимо аппроксимировать их показательной функцией (чаще всего используется экспонента) и убедиться, что коэффициент аппроксимации составляет 0,9 и выше. Если при этом отсутствуют правила, детерминантно определяющие значение того или иного числа, то метод бенфорда к вашим данным применим.

Алгоритм применения бенфорд-метода в программах LibreOfficeCalc и MS Excel 

1. Исходные данные

Со страницы сайта oopt.aari.ru, разработанного ФГБУ «ААНИИ» и Лабораторией геоинформационных технологий взят перечень особо охраняемых природных территорий России. Список насчитывает 8013 ООПТ, из которых 4410 войдут в нашу обработку. Это действующие или реорганизованные ООПТ, для которых есть данные по площади.

Данные по площади лесных пожаров взяты с сайта федерального агентства лесного хозяйства. Выборка охватывает данные по всем регионам России с первого квартала 2009 года по второй квартал 2013 года. Всего за этот период было охвачено лесным пожаром 949 территорий различной площади.

2. Проверка на распределение

Нам необходимо убедиться, что данные подчиняются экспоненциальному распределению. Сортируем данные по площади и аппроксимируем их экспонентой.

Lj2-24

На рисунках изображены площади ООПТ (верхний рисунок) и площади пожаров (нижний рисунок), отсортированные по значению. Ось ординат показывает площадь в гектарах.   Чем больше площадь особо охраняемой природной территории, тем меньше таких ООПТ в стране. Равно как и значительные площади подвергаются пожарам гораздо реже небольших участков.  Коэффициент аппроксимации обоих наборов данных экспонентой (синяя линия) составил 0,98.

3. Избавление от нулей

Отличительной особенностью фрактальных множеств, к которым относятся и наши данные является их масштабная инвариантность. Распределение не зависит от единиц в которых выражены величины. Будь наши данные выражены в километрах, миллиметрах или ангстремах, мы всегда будем наблюдать одинаковые закономерности.  Масштабная инвариантность позволяет нам избавиться от значений менее единицы простым умножением на 100 (в каждом конкретном случае может быть различный порядок, в зависимости от наименьшего числа в выборке. В нашем случае таким числом было 0,01). Сделать это необходимо, поскольку формула Бенфорда использует логарифмы, а потому не работает с нулевыми числами.

4. Отделение первой цифры и расчет

Методом LEFT() в LibreOfficeCalc или ЛЕВСИМВ() в Excel отделяем первую цифру из каждого числа. Получившийся столбец с первыми цифрами чисел сортируем и подсчитываем количество единиц, двоек, троек и т.д. до девяток. Вероятность встречи каждой цифры рассчитываем как отношение количества чисел, начинающихся с данной цифры к общему количеству чисел. Например, если в выборке по пожарам было 273 числа, начинающихся на единицу, а общий объем выборки 949, то вероятность того, что первой цифрой в числе будет единица составит 100%*273/949=28,8%.   В итоге у вас получится аналог вот таких таблиц (верхняя таблица — данные по площади ООПТ, нижняя таблица — данные по площади пожаров):

Lj2-25

По ним же, для большей наглядности можно построить соответствующие графики сравнения фактической и расчетной бенфорд-последовательности (вверху для площади ООПТ, внизу для площади лесных пожаров):

Lj2-252

Стобцы на графиках соответствуют фактической бенфорд-последовательности, красная линия соответствует теоретической последовательности, рассчитанной по формуле Бенфорда.

Приведенные графики свидетельствуют, что данные по площадям ООПТ России и данные по площади пожаров за 2009-2013 г. достоверны. Наибольшие ошибки приходятся на крайние значения, что связано со сложностью определения массовых (ошибки по единице) и крупных (ошибки по девятке) объектов в натуре, а также с меньшим объемом статистических данных (ошибки по девятке).

В случае, если бы анализируемые нами выборки были сфальцифицированы рандомным методом, то есть, вместо реальных значений были указаны случайные числа, фактическая и расчетная бенфорд-последовательности различались бы радикально.

P.S. Да, я знаю, что качество приведенных картинок отвратительно. Но поверьте, вы встретились с ними в странный момент их жизни.