Добавление местной координатной системы в GIS: различия между версиями

Материал из GIS-Lab
Перейти к навигации Перейти к поиску
 
(не показано 150 промежуточных версий 2 участников)
Строка 1: Строка 1:
{{Статья|Черновик}}
{{Статья|Опубликована|local-cs}}
 
{{Аннотация|Конструирование проекций, имитирующих местные координатные системы, в QGIS}}


== Введение ==
== Введение ==


Под местной системой координат (МСК) будет подразумеваться так называемая «городская» система, построенная независимо от государственной системы (ГСК) и включенная в неё [[Конформное преобразование|заданием ключей перехода к СК-42 или СК-63]]. МСК крупных территорий, сравнимых с размерами субъектов Федерации, не являются предметом данной статьи, поскольку относятся к классическим картографическим проекциям.
Под местной системой координат (МСК) будет подразумеваться так называемая «городская» система, построенная независимо от государственной системы (ГСК) и включенная в неё [http://gis-lab.info/qa/helmert2d.html#.D0.92.D0.B2.D0.B5.D0.B4.D0.B5.D0.BD.D0.B8.D0.B5 заданием ключей перехода к СК-42 или СК-63]. МСК крупных территорий, сравнимых с размерами субъектов Федерации, не являются предметом данной статьи, поскольку относятся к классическим картографическим проекциям.
 
Многие программы ГИС по примеру геодезических программ позволяют реализовать работу в МСК непосредственно. Так, в QGIS и в MapInfo Pro любая проекция может быть дополнена аффинным преобразованием, а в Global Mapper конформные проекции дополняются разворотом. В данной статье рассматривается создание МСК в программах QGIS и MapInfo.


== Постановка задачи ==
== Постановка задачи ==


Некоторые программы позволяют реализовать работу в МСК непосредственно. Так, в MapInfo любая проекция может быть дополнена аффинным преобразованием. ArcGIS в качестве МСК предлагает локальную проекцию: аналог ортометрической проекции на эллипсоиде, дополненный разворотом и масштабированием.
Имеется множество пунктов, для которых известны координаты ''X'', ''Y'' в ГСК и ''x'', ''y'' в МСК. Требуется подобрать проекцию, удовлетворительно представляющую МСК в ГИС. При подборе параметров предполагается использовать один из пунктов в качестве центральной точки преобразования.
 
Другие программы, включая QGIS, работают только с «обыкновенными» проекциями, не допуская дополнительных геометрических преобразований. Задача статьи — показать, как сконструировать проекцию, позволяющую работать в МСК в таких средах, как QGIS или, скажем, бортовой софт приёмников GARMIN.
 
В качестве рабочей среды будем использовать командную строку UNIX. Это идеальный инструмент для экспериментирования, позволяющий непринуждённо сочетать PROJ.4, [[%D0%9A%D0%BE%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5#.D0.9F.D1.80.D0.B8.D0.BC.D0.B5.D1.80_.D0.BF.D1.80.D0.BE.D0.B3.D1.80.D0.B0.D0.BC.D0.BC.D0.BD.D0.BE.D0.B9_.D1.80.D0.B5.D0.B0.D0.BB.D0.B8.D0.B7.D0.B0.D1.86.D0.B8.D0.B8|'''findkey''']] и утилиты обработки текстовых потоков '''awk''', '''pr'''.<ref>Команды примера воспроизводятся в среде MinGW, что входит в состав QGIS под MS Windows. Две особенности:
* нужно заменить команду '''awk''' на '''gawk''', или лучше в системе определить '''awk''' как синоним '''gawk''';
* после некоторых команд ('''proj''', '''pr''') придётся добавить в пайп команду удаления лишних символов CR:<syntaxhighlight lang="bash">| tr -d '\r'</syntaxhighlight>
</ref>


== Подготовка данных ==
== Подготовка данных ==


Тестовый пример основан на сгенерированных данных. В таблице ''X'', ''Y'' — координаты пункта в государственной системе (ГСК), а именно в седьмой зоне СК-42, ''x'', ''y'' — координаты в местной системе (МСК), ''p'' — вес пункта.
=== Исходные данные ===


{| class="wikitable"
Имеются два каталога. Текстовый файл '''cat_s42z4.tsv''' содержит координаты пунктов в государственной системе (ГСК), а именно в четвёртой зоне СК-42:
|-
! ''ID'' !! ''X'' !! ''Y'' !! ''x'' !! ''y'' !! ''p''
|- align="right"
| 1 || 7383477.64 || 6087377.60 || 1334.71  || 285.94 || 1.0
|- align="right"
| 2 || 7382557.14 || 6081916.51  || 563.67 || −5197.34 || 1.0
|- align="right"
| 3 || 7386610.19 || 6088160.39 || 4444.27  || 1153.79 || 1.0
|- align="right"
| 4 || 7381962.05 || 6090016.34 || −252.07  || 2881.90 || 1.0
|}
 
Подготовим файл исходных данных ''data0.dat'':


<pre>
<pre>
1 7383477.64 6087377.60 1334.71  285.94 1.0
4645997.49 5768521.60
2 7382557.14 6081916.51  563.67 -5197.34 1.0
4661392.15 5770068.91
3 7386610.19 6088160.39 4444.27  1153.79 1.0
4650059.09 5783332.41
4 7381962.05 6090016.34 -252.07  2881.90 1.0
4634241.37 5778952.22
4631481.69 5764570.61
4642125.18 5754643.12
4655952.19 5757337.28
</pre>
</pre>


Убедимся в однородности данных обоих каталогов. Для этого вычислим параметры конформного преобразования:
В файле '''cat_local.tsv''' — координаты в местной системе (МСК):
 
<syntaxhighlight lang="bash">
$ findkey data0.dat key0.dat var0.dat
</syntaxhighlight>
 
Сами по себе параметры нас не интересуют. Важны величины невязок, выведенных в файл '''var0.dat''':


<pre>
<pre>
1 … -0.002 -0.001
67266.64 30088.40
2 … -0.017 0.013
82697.29 31184.27
3 … 0.031 0.017
71759.40 44771.50
4 … -0.012 -0.029
55822.67 40857.06
52643.65 26564.42
62990.64 16331.35
76888.20 18619.57
</pre>
</pre>


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


Пересчитаем координаты из ГСК в долготу/широту:
=== Дополнительные данные ===


<syntaxhighlight lang="bash">
Очень важно помнить, что с точки зрения математической картографии МСК остаётся проекцией Гаусса-Крюгера и наследует её искажения. Поэтому важно знать, на какой именно ГСК основана МСК. Зачастую это заранее неизвестно, и приходится проводить предварительное исследование для выяснения этого вопроса.
$ awk '{print $2, " ", $3}' data0.dat | proj -I -f "%.9f" +proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=7500000 +y_0=0 +ellps=krass > pt_longlat.dat
</syntaxhighlight>


Команда '''awk''' извлекает из файла '''data0.dat''' вторую и третью колонки и передаёт их утилите '''proj''', которая пересчитывает координаты из седьмой зоны СК-42 в долготу/широту и записывает результаты в файл '''pt_longlat.dat''':
В нашем примере мы предполагаем, что базовая ГСК либо СК-42 зона 4, либо СК-63 зона C0.
Каталог в первой системе имеется, нужно подготовить каталог в альтернативной системе.


<pre>
Параметры СК-63 зона C0 известны, это EPSG:3350 "Pulkovo 1942 / CS63 zone C0".  
37.183749701 54.896961118
Создадим каталог в СК-63 с помощью утилиты '''proj''':
37.171630976 54.847714659
37.232243035 54.904709276
37.159058387 54.920296878
</pre>


== Переходная проекция ==
Создадим в качестве переходной проекцию Гаусса-Крюгера, осевой меридиан и начальная параллель которой пересекаются в первом пункте, а координаты в этой точке совпадают с координатами МСК этого пункта. Вычислим координаты в переходной проекции и запишем их в файл '''pt_tmerc''':
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ proj -f "%.4f" +proj=tmerc +lat_0=54.896961118 +lon_0=37.183749701 +k=1 +x_0=1334.71 +y_0=285.94 +ellps=krass pt_longlat.dat > pt_tmerc.dat
$ proj -I -f "%.17g" +init=epsg:28404 cat_s42z4.tsv > lonlat.tsv
$ proj -f "%.17g" +proj=tmerc +lat_0=0.1 +lon_0=21.95 +k=1 +x_0=250000 +y_0=0 +ellps=krass lonlat.tsv > cat_s63c0.tsv
</syntaxhighlight>
</syntaxhighlight>


Содержимое '''pt_tmerc.dat''':
В файл '''lonlat.tsv''' запишутся географические координаты (долготы и широты) в СК-42, а в '''cat_s63c0.tsv''' координаты в СК-63 зона C0:


<pre>
<pre>
1334.7100 285.9400
330797.45370592922 5755981.4751984337
556.2353 -5196.2593
346208.04426388565 5757327.0953416284
4445.4005 1149.5698
335051.73824425979 5770735.1441946141
-248.5461 2884.0431
319180.795365442  5766563.182877643
316233.72446517192 5752221.1970210578
326744.90098082455 5742157.2318198672
340603.31654394628 5744670.1910534762
</pre>
</pre>


Подготовим входной файл '''data1.dat''' для '''findkey'''. Нужно на место второй и третьей колонок в '''data0.dat''' записать данные '''pt_tmerc.dat'''.
== Создание МСК ==


<syntaxhighlight lang="bash">
Для вычислений используем консольную утилиту '''findkey''', о которой сказано ниже в приложении.
$ pr -m -t -s\  data0.dat pt_tmerc.dat | awk '{print $1, $7, $8, $4, $5, $6}' > data1.dat
</syntaxhighlight>


Команда '''pr''' построчно объединяет данные из шести колонок '''data0.dat''' и двух колонок '''pt_tmerc.dat''' в общую строку, команда '''awk''' выводит в '''data1.dat''' нужные колонки в нужном же порядке:
=== Определение базовой ГСК ===


<pre>
Вычислим параметры конформного преобразования координат из СК-42 в МСК. Для этого в командной строке запустим программу '''findkey''' с аргументами '''cat_s42z4.tsv''' и '''cat_local.tsv''':
1 1334.7100 285.9400 1334.71 285.94 1.0
2 556.2353 -5196.2593 563.67 -5197.34 1.0
3 4445.4005 1149.5698 4444.27 1153.79 1.0
4 -248.5461 2884.0431 -252.07 2881.90 1.0
</pre>
 
Нетрудно заметить, что координаты первого пункта в переходной проекции совпадают с таковыми в МСК. Для остальных пунктов это не так, что говорит о наличии масштабирования и разворота. Убедимся в этом:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ findkey data1.dat key1.dat var1.dat
$ findkey cat_s42z4.tsv cat_local.tsv
</syntaxhighlight>
</syntaxhighlight>


Содержимое '''key1.dat''':
Программа создаст два файла: '''key.txt''' с параметрами конформного преобразования и '''var.csv''' с координатными невязками.
Посмотрим на содержимое '''var.csv''':


<pre>
{| class="wikitable"
0.390
|-
-1.813
|- align="right"
1.0000052644
| -0.006 || 0.007
0.0013554914
|- align="right"
1.0000061831
| 0.182 || 0.046
+0.07766348
|- align="right"
</pre>
| -0.166 || 0.110
|- align="right"
| 0.019 || -0.185
|- align="right"
| 0.148 || 0.100
|- align="right"
| -0.146 || 0.094
|- align="right"
| -0.031 || -0.171
|}


Первые четыре параметра — ''a''<sub>0</sub>, ''b''<sub>0</sub>, ''a''<sub>1</sub>, ''b''<sub>1</sub>, оставшиеся два — масштабный множитель ''m'' и разворот ''θ'' в градусах.
Вычислим параметры конформного преобразования координат из СК-63 в МСК:
 
== Косая проекция Меркатора ==
 
Всё готово для конструирования косой проекции Меркатора. Зададим, как для переходной проекции, долготу и широту первого пункта в качестве начальных, его координаты в МСК в качестве «фальшивых». Кроме того, перенесём масштаб конформного преобразования в масштаб проекции, а разворот — в азимут линии минимального масштаба. Вычислим координаты в этой проекции и запишем в файл '''pt_omerc.dat''':


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ proj -f "%.4f" +proj=omerc +lat_0=54.896961118 +lonc=37.183749701 +alpha=0.07766348 +gamma=0 +k=1.0000061831 +x_0=1334.71 +y_0=285.94 +ellps=krass pt_longlat.dat | tr -d '\r' > pt_omerc.dat
$ findkey cat_s63c0.tsv cat_local.tsv
</syntaxhighlight>
</syntaxhighlight>


Вычислим параметры конформного преобразования для '''pt_omerc.dat''': ''m'' = 1.0000000000, ''θ'' = −0.00000022° = −0.0008″. Практически масштаб сведён к единице, а разворот к нулю, т.е. проекция, эквивалентная МСК, построена.
Теперь содержимое '''var.csv''' будет таким:


== Проекция Гаусса-Крюгера ==
{| class="wikitable"
|-
|- align="right"
| 0.000 || -0.002
|- align="right"
| -0.001 || 0.002
|- align="right"
| -0.001 || 0.002
|- align="right"
| 0.004 || 0.000
|- align="right"
| -0.002 || 0.001
|- align="right"
| 0.002 || -0.002
|- align="right"
| -0.002 || -0.001
|}


Встречаются программы, набор проекций в которых ограничен. Проекция Гаусса-Крюгера найдётся в самом аскетичном наборе. В приёмниках GARMIN пользовательская проекция может быть задана только как Transverse Mercator.
Сравнение невязок позволяет сделать вывод, что базовая ГСК — СК-63 зона C0.


=== Долгота осевого меридиана ===
=== Полученные параметры ===


Если угол разворота не превышает первых десятков угловых минут, можно компенсировать его переносом осевого меридиана в сторону и не бояться потери геодезической точности. Даже первые градусы не должны существенно повлиять на навигационные приложения. Дальнейшее увеличение угла приводит к стремительному росту градиента масштаба в направлении запад-восток и, следовательно, искажению подобия фигур.
Изучим содержимое файла '''key.txt''', соответствующего СК-63:


В нашем примере разворот оказался равным ''θ'' = 0.07766348° = 0°04′39.59″, следовательно, в данном случае на основе проекции Гаусса-Крюгера можно создать довольно точный аналог МСК.
<pre>
WKT:
A0 = -356718.938772419
A1 = 0.999887380509183
A2 = 0.0161962611321084
B0 = -5719887.1597502
B1 = -0.0161962611321084
B2 = 0.999887380509183


Предварительное значение перемещения осевого меридиана можно оценить по формуле:
MapInfo:
0.999887380509, 0.016196261132, -356718.93877241889, -0.016196261132, 0.999887380509, -5719887.159750198


<math>\Delta L = -\operatorname{arc\,tg}\frac{\operatorname{tg} \theta}{\sin B}</math>
Alternate set:
scale = 1.000018546116108
angle = 0.92800077023443683
</pre>


где ∆''L'' — перенос осевого меридиана, ''θ'' — угол разворота, ''B'' — широта первого пункта. Получаем ∆''L'' = −0.0949292655, ''L'' = 37.0888204345. Построим проекцию Гаусса-Крюгера на основе переходной, подставив ''L'' в долготу осевого меридиана и нули в «фальшивые» координаты:
Мы видим три группы чисел: WKT, MapInfo и Alternate set.


<syntaxhighlight lang="bash">
Обратим внимание на параметр разворота angle из третьей группы. Если он мал, в пределах первых десятых долей градуса, имеет смысл отказаться от использования конформного преобразования и вместо этого смещать осевой меридиан для устранения угла разворота.
$ proj -f "%.4f" +proj=tmerc +lat_0=54.8969611 +lon_0=37.0888204 +k=1 +x_0=0 +y_0=0 +ellps=krass pt_longlat.dat > pt_tmerc.dat
</syntaxhighlight>


Последующее вычисление параметров конформного преобразования выдаёт сравнительно большой угол разворота. Однако уже после второй итерации с ''L'' = 37.0888079 получаем ''m'' = 1.0000057306, ''θ'' = +0.00000044.
=== Создание МСК в MapInfo ===


=== Масштаб проекции ===
Запись базовой СК-63 зона C0 в файле '''MAPINFOW.PRJ''' должна выглядеть так:


Перенесём ''m'' в масштаб проекции:
<pre>
"CS63 zone C0", 8, 1001, 7, 21.95, 0.1, 1, 250000, 0
</pre>


<syntaxhighlight lang="bash">
Используем вторую группу из файла '''key.txt'''. Впишем МСК в '''MAPINFOW.PRJ''' как базовую, дополненную аффинным преобразованием:
+proj=tmerc +lat_0=54.8969611 +lon_0=37.0888079 +k=1.00000573 +x_0=0 +y_0=0 +ellps=krass
</syntaxhighlight>


Новое значение ''m'' = 1.0000000039, т.е. практически единица.
<pre>
"Biala Podlaska", 1008, 1001, 7, 21.95, 0.1, 1, 250000, 0, 7, 0.999887380509, 0.016196261132, -356718.93877241889, -0.016196261132, 0.999887380509, -5719887.159750198
</pre>


=== Координаты в начальной точке проекции ===
Полноценное определение нуждается в параметрах Bounds:


После очередного вычисления параметров конформного преобразования обратим внимание на первичные параметры ''a''<sub>0</sub> = −4756.693 и ''a''<sub>1</sub> = 281.807. Перенесём эти значения в параметры проекции '''x_0''' и '''y_0''':
<pre>
"Biala Podlaska", 3008, 1001, 7, 21.95, 0.1, 1, 250000, 0, 7, 0.999887380509, 0.016196261132, -356718.93877241889, -0.016196261132, 0.999887380509, -5719887.159750198, 52000, 15000, 82000, 45000
</pre>
 
=== Создание МСК в QGIS ===


<syntaxhighlight lang="bash">
Возьмём коэффициенты из первой группы в файле '''key.txt''' и создадим МСК в формате WKT как аффинное преобразование на основе проекции "Pulkovo 1942 / CS63 zone C0":
+proj=tmerc +lat_0=54.8969611 +lon_0=37.0888079 +k=1.00000573 +x_0=-4756.69 +y_0=281.81 +ellps=krass
</syntaxhighlight>


Проекция создана.
<pre>
DERIVEDPROJCRS["Biala Podlaska",
    BASEPROJCRS["Pulkovo 1942 / CS63 zone C0",
        BASEGEOGCRS["Pulkovo 1942",
            DATUM["Pulkovo 1942",
                ELLIPSOID["Krassowsky 1940",6378245,298.3,
                    LENGTHUNIT["metre",1]]],
            PRIMEM["Greenwich",0,
                ANGLEUNIT["Degree",0.0174532925199433]]],
        CONVERSION["CS63 zone C0",
            METHOD["Transverse Mercator",
                ID["EPSG",9807]],
            PARAMETER["Latitude of natural origin",0.1,
                ANGLEUNIT["degree",0.0174532925199433],
                ID["EPSG",8801]],
            PARAMETER["Longitude of natural origin",21.95,
                ANGLEUNIT["degree",0.0174532925199433],
                ID["EPSG",8802]],
            PARAMETER["Scale factor at natural origin",1,
                SCALEUNIT["unity",1],
                ID["EPSG",8805]],
            PARAMETER["False easting",250000,
                LENGTHUNIT["metre",1],
                ID["EPSG",8806]],
            PARAMETER["False northing",0,
                LENGTHUNIT["metre",1],
                ID["EPSG",8807]]]],
    DERIVINGCONVERSION["Affine",
        METHOD["Affine parametric transformation",
            ID["EPSG",9624]],
        PARAMETER["A0",-356718.938772649,
            LENGTHUNIT["metre",1],
            ID["EPSG",8623]],
        PARAMETER["A1",0.999887380509304,
            SCALEUNIT["coefficient",1],
            ID["EPSG",8624]],
        PARAMETER["A2",0.0161962611321413,
            SCALEUNIT["coefficient",1],
            ID["EPSG",8625]],
        PARAMETER["B0",-5719887.15975089,
            LENGTHUNIT["metre",1],
            ID["EPSG",8639]],
        PARAMETER["B1",-0.0161962611321413,
            SCALEUNIT["coefficient",1],
            ID["EPSG",8640]],
        PARAMETER["B2",0.999887380509304,
            SCALEUNIT["coefficient",1],
            ID["EPSG",8641]]],
    CS[Cartesian,2],
        AXIS["easting (X)",east,
            ORDER[1],
            LENGTHUNIT["metre",1]],
        AXIS["northing (Y)",north,
            ORDER[2],
            LENGTHUNIT["metre",1]],
    USAGE[
        SCOPE["unknown"],
        AREA["Europe - Poland - Biala Podlaska"],
        BBOX[51.9,22.9,52.1,23.3]]]
</pre>


=== Альтернативный набор параметров ===
В первой строке записали название системы координат латиницей "Biala Podlaska".
В конце вставили название покрываемой территории "Europe - Poland - Biala Podlaska" и охват в формате ''φ''<sub>min</sub>, ''λ''<sub>min</sub>, ''φ''<sub>max</sub>, ''λ''<sub>max</sub>.


Пользовательская проекция старых моделей навигаторов GARMIN допускает ограниченный набор параметров. В ней не используется '''lat_0'''. Компенсируем его отсутствие коррекцией '''y_0'''. Напишем и запустим скрипт:
=== Создание МСК в Global Mapper ===


<syntaxhighlight lang="bash">
Третья группа параметров в файле '''key.txt''' содержит масштабный коэффициент scale и угол поворота angle. Угол вставляем как есть, а масштабный коэффициент умножим на соответствующий параметр базовой СК.
proj -f "%.4f" +proj=tmerc +lat_0=54.8969611 +lon_0=37.0888079 +k=1.00000573 +x_0=-4756.69 +y_0=281.81 +ellps=krass <<EOF
37.0888079 0
EOF
</syntaxhighlight>


Вывод скрипта даёт координаты пересечения осевого меридиана с экватором:
== Заключение ==


<pre>-4756.6900 -6085619.5031</pre>
Задачи построения МСК для QGIS и MapInfo выполнены, цель достигнута.


Вот эквивалентный набор параметров:
== Приложение. Утилита findkey ==


<pre>+proj=tmerc +lat_0=0 +lon_0=37.0888079 +k=1.00000573 +x_0=-4756.69 +y_0=-6085619.50 +ellps=krass</pre>
Программа '''findkey''' вычисляет параметры конформного преобразования. Написана на языке C. Вот листинг:


Может возникнуть вопрос, почему нельзя было сразу начать со значения '''lat_0''' = 0, а не с параллели первого пункта? Ответ прост: устойчивые решения получаются только в окрестности заданных пунктов.
<syntaxhighlight lang="c">
#include <stdio.h>
#include <stdlib.h>
#include <math.h>


== Коническая равноугольная проекция Ламберта ==
#define SEP ';' /* var-file column separator */


Эта проекция по использованию в геодезии занимает второе место после проекции Гаусса-Крюгера. Она, в отличие от последней, предоставляет полную свободу в выборе центрального меридиана, поскольку градиент масштаба вдоль параллели равен нулю.
/* --------------------------------------------------------------------------
* findkey
*
* Program to compute Helmert 2D transformation parameters
*
* Usage: findkey <coord1> <coord2>
*
* Input files: coord1 coord2
*    coord1 - source coordinate 'x1 y1'
*    coord2 - destination coordinate 'x2 y2'
*              a row per a point; 3+ points
*
* Output files:
*    key.txt - transformation parameters
*    var.csv - SEP separated residuals 'dx dy'
* -------------------------------------------------------------------------- */
int main(int argc, char *argv[])
{
  char buf0[1024], buf1[1024];
  double x[2], y[2];
  double xc[2], yc[2];
  double dx[2], dy[2];
  double s[7] = {0., 0., 0., 0., 0.};
  double det, h[6];
  double mu, theta;
  double yh[2];
  int i;
  FILE *fp0, *fp1, *fp2;


Редко встречается бочка мёда без ложки дёгтя. Некоторые программы (MapInfo, например) предоставляют возможность задания этой проекции только в варианте с двумя стандартными параллелями, в то время как нам нужна единственная стандартная параллель, проходящая через выбранный пункт. По единственной стандартной параллели и масштабу на ней можно вычислить две стандартные широты тогда и только тогда, когда масштаб на единственной параллели меньше единицы. В общем, возможность имитации МСК таким вариантом с двумя стандартами 50:50.
  if (argc < 3) {
    printf("Usage: findkey <coord1> <coord2>\n");
    exit(EXIT_FAILURE);
  }


К счастью, PROJ.4 не имеет этого досадного ограничения, и коническая равноугольная проекция Ламберта является неплохим вариантом создания аналога МСК для QGIS.
  if ((fp0 = fopen(argv[1], "r")) == NULL) {
    printf("can't open %s\n", argv[1]);
    exit(EXIT_FAILURE);
  }


=== Долгота центрального меридиана ===
  if ((fp1 = fopen(argv[2], "r")) == NULL) {
    printf("can't open %s\n", argv[2]);
    exit(EXIT_FAILURE);
  }


По развороту ''θ'' = 0.07766348° определим величину переноса центрального меридиана:
  /* coordinate sums */
  while (fgets(buf0, 1024, fp0) != NULL && fgets(buf1, 1024, fp1) != NULL) {
    sscanf(buf0, "%lf %lf", &x[0], &x[1]);
    sscanf(buf1, "%lf %lf", &y[0], &y[1]);
    s[0] += x[0];
    s[1] += x[1];
    s[2] += y[0];
    s[3] += y[1];
    s[4] += 1.;
  }
  rewind(fp0);
  rewind(fp1);


<math>\Delta L = -\frac{\theta}{\sin B}</math>
  /* centrum gravitatis */
  for (i = 0; i < 2; i++) {
    xc[i] = s[i] / s[4];
    yc[i] = s[2 + i] / s[4];
  }


Получим ∆''L'' = −0.0949292942, ''L'' = 37.0888204068. Запишем ''L'' в долготу центрального меридиана, нули в '''x_0''' и '''y_0''':
  /* sums of products */
  for (i = 0; i < 7; i++)
    s[i] = 0.;
  while (fgets(buf0, 1024, fp0) != NULL && fgets(buf1, 1024, fp1) != NULL) {
    sscanf(buf0, "%lf %lf", &x[0], &x[1]);
    sscanf(buf1, "%lf %lf", &y[0], &y[1]);
    /* coordinate differences */
    dx[0] = x[0] - xc[0];
    dx[1] = x[1] - xc[1];
    dy[0] = y[0] - yc[0];
    dy[1] = y[1] - yc[1];
    /* summation */
    s[0] += dx[0] * dx[0];
    /*s[1] += dx[0] * dx[1];*/
    s[2] += dx[1] * dx[1];
    s[3] += dx[0] * dy[0];
    s[4] += dx[1] * dy[0];
    s[5] += dx[0] * dy[1];
    s[6] += dx[1] * dy[1];
  }
  rewind(fp0);
  rewind(fp1);


<syntaxhighlight lang="bash">
  /* Helmert parameters */
$ proj -f "%.4f" +proj=lcc +lat_0=54.8969611 +lon_0=37.0888204 +lat_1=54.8969611 +k_0=1 +x_0=0 +y_0=0 +ellps=krass pt_longlat.dat > pt_lcc.dat
  det = s[0] + s[2];
</syntaxhighlight>
  h[0] = (s[3] + s[6]) / det;
  h[1] = (s[4] - s[5]) / det;
  h[2] = yc[0] - h[0] * xc[0] - h[1] * xc[1];
  h[3] = -h[1];
  h[4] = h[0];
  h[5] = yc[1] - h[3] * xc[0] - h[4] * xc[1];


Через пять итераций приходим к значению '''lon_0''' = 37.0824027, при котором ''θ'' практически сводится к нулю.
  /* alternative Helmert parameter set */
  mu = hypot(h[0], h[1]);
  theta = atan2(h[1], h[0]) / M_PI * 180.;


=== Масштаб проекции ===
  /* output parameters */
  if ((fp2 = fopen("key.txt", "w")) == NULL) {
    printf("can't create %s\n", "key.txt");
    exit(EXIT_FAILURE);
  }
  fprintf(fp2, "WKT:\nA0 = %.15g\nA1 = %.15g\nA2 = %.15g\nB0 = %.15g\nB1 = %.15g\nB2 = %.15g\n",
  h[2], h[0], h[1], h[5], h[3], h[4]);
  fprintf(fp2, "\nMapInfo:\n%.12f, %.12f, %.17g, %.12f, %.12f, %.17g\n",
  h[0], h[1], h[2], h[3], h[4], h[5]);
  fprintf(fp2, "\nAlternate set:\nscale = %.17g\nangle = %.17g\n", mu, theta);
  fclose(fp2);


Полученное последним значение ''m'' = 1.00002378 запишем в параметр '''k_1''':
  /* output residuals */
  if ((fp2 = fopen("var.csv", "w")) == NULL) {
    printf("can't create %s\n", "var.csv");
    exit(EXIT_FAILURE);
  }
  while (fgets(buf0, 1024, fp0) != NULL && fgets(buf1, 1024, fp1) != NULL) {
    sscanf(buf0, "%lf %lf", &x[0], &x[1]);
    sscanf(buf1, "%lf %lf", &y[0], &y[1]);
    /* model y */
    yh[0] = h[0] * x[0] + h[1] * x[1] + h[2];
    yh[1] = h[3] * x[0] + h[4] * x[1] + h[5];
    fprintf(fp2, "%.15g%c%.15g\n", yh[0] - y[0], SEP, yh[1] - y[1]);
  }
  fclose(fp2);
  fclose(fp1);
  fclose(fp0);


<syntaxhighlight lang="bash">
  exit(EXIT_SUCCESS);
+proj=lcc +lat_0=54.8969611 +lon_0=37.0824027 +lat_1=54.8969611 +k_0=1.00002378 +x_0=0 +y_0=0 +ellps=krass
}
</syntaxhighlight>
</syntaxhighlight>


=== Координаты в начале проекции ===
Сохраним код в файл '''findkey.c'''. Создадим исполняемый модуль компилятором '''gcc''':
 
Перенесём параметры конформного преобразования ''a''<sub>0</sub> = −5167.777 и ''a''<sub>1</sub> = 281.494 в параметры проекции '''x_0''', '''y_0''':


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
+proj=lcc +lat_0=54.8969611 +lon_0=37.0824027 +lat_1=54.8969611 +k_0=1.00002378 +x_0=-5167.78 +y_0=281.49 +ellps=krass
$ gcc -o findkey findkey.c -lm
</syntaxhighlight>
</syntaxhighlight>


Проекция создана.
Пользователи MS Windows могут загрузить уже скомпилированную [https://wiki.gis-lab.info/images/4/43/Findkey.zip программу].
 
== Примечания ==


<references />
== Ссылки ==


== Ссылки по теме ==
* [http://pubs.usgs.gov/pp/1395/report.pdf Map Projections — A Working Manual, Snyder J. P., USGS Professional Paper 1395, 1987]
* [http://www.epsg.org/Portals/0/373-07-2.pdf Coordinate Conversions and Transformations including Formulas, EPSG Guidance Note 7-2]
* [https://desktop.arcgis.com/ru/arcmap/latest/map/projections/local-cartesian-projection.htm Справка ArcGIS — Локальная проекция Декартовой системы координат]
* [https://proj4.org/usage/index.html Using PROJ]
* [http://gis-lab.info/qa/helmert2d.html Конформное преобразование]
* [https://gis.stackexchange.com/questions/353022/defining-a-coordinate-system-in-wkt-or-proj-format-that-has-an-affine-transforma Defining a coordinate system in WKT or PROJ format that has an Affine transformaiton and bounds]

Текущая версия от 16:56, 3 сентября 2020

Эта страница опубликована в основном списке статей сайта
по адресу http://gis-lab.info/qa/local-cs.html


Конструирование проекций, имитирующих местные координатные системы, в QGIS

Введение

Под местной системой координат (МСК) будет подразумеваться так называемая «городская» система, построенная независимо от государственной системы (ГСК) и включенная в неё заданием ключей перехода к СК-42 или СК-63. МСК крупных территорий, сравнимых с размерами субъектов Федерации, не являются предметом данной статьи, поскольку относятся к классическим картографическим проекциям.

Многие программы ГИС по примеру геодезических программ позволяют реализовать работу в МСК непосредственно. Так, в QGIS и в MapInfo Pro любая проекция может быть дополнена аффинным преобразованием, а в Global Mapper конформные проекции дополняются разворотом. В данной статье рассматривается создание МСК в программах QGIS и MapInfo.

Постановка задачи

Имеется множество пунктов, для которых известны координаты X, Y в ГСК и x, y в МСК. Требуется подобрать проекцию, удовлетворительно представляющую МСК в ГИС. При подборе параметров предполагается использовать один из пунктов в качестве центральной точки преобразования.

Подготовка данных

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

Имеются два каталога. Текстовый файл cat_s42z4.tsv содержит координаты пунктов в государственной системе (ГСК), а именно в четвёртой зоне СК-42:

4645997.49 5768521.60
4661392.15 5770068.91
4650059.09 5783332.41
4634241.37 5778952.22
4631481.69 5764570.61
4642125.18 5754643.12
4655952.19 5757337.28

В файле cat_local.tsv — координаты в местной системе (МСК):

67266.64 30088.40
82697.29 31184.27
71759.40 44771.50
55822.67 40857.06
52643.65 26564.42
62990.64 16331.35
76888.20 18619.57

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

Дополнительные данные

Очень важно помнить, что с точки зрения математической картографии МСК остаётся проекцией Гаусса-Крюгера и наследует её искажения. Поэтому важно знать, на какой именно ГСК основана МСК. Зачастую это заранее неизвестно, и приходится проводить предварительное исследование для выяснения этого вопроса.

В нашем примере мы предполагаем, что базовая ГСК либо СК-42 зона 4, либо СК-63 зона C0. Каталог в первой системе имеется, нужно подготовить каталог в альтернативной системе.

Параметры СК-63 зона C0 известны, это EPSG:3350 "Pulkovo 1942 / CS63 zone C0". Создадим каталог в СК-63 с помощью утилиты proj:

$ proj -I -f "%.17g" +init=epsg:28404 cat_s42z4.tsv > lonlat.tsv
$ proj -f "%.17g" +proj=tmerc +lat_0=0.1 +lon_0=21.95 +k=1 +x_0=250000 +y_0=0 +ellps=krass lonlat.tsv > cat_s63c0.tsv

В файл lonlat.tsv запишутся географические координаты (долготы и широты) в СК-42, а в cat_s63c0.tsv координаты в СК-63 зона C0:

330797.45370592922 5755981.4751984337
346208.04426388565 5757327.0953416284
335051.73824425979 5770735.1441946141
319180.795365442   5766563.182877643
316233.72446517192 5752221.1970210578
326744.90098082455 5742157.2318198672
340603.31654394628 5744670.1910534762

Создание МСК

Для вычислений используем консольную утилиту findkey, о которой сказано ниже в приложении.

Определение базовой ГСК

Вычислим параметры конформного преобразования координат из СК-42 в МСК. Для этого в командной строке запустим программу findkey с аргументами cat_s42z4.tsv и cat_local.tsv:

$ findkey cat_s42z4.tsv cat_local.tsv

Программа создаст два файла: key.txt с параметрами конформного преобразования и var.csv с координатными невязками. Посмотрим на содержимое var.csv:

-0.006 0.007
0.182 0.046
-0.166 0.110
0.019 -0.185
0.148 0.100
-0.146 0.094
-0.031 -0.171

Вычислим параметры конформного преобразования координат из СК-63 в МСК:

$ findkey cat_s63c0.tsv cat_local.tsv

Теперь содержимое var.csv будет таким:

0.000 -0.002
-0.001 0.002
-0.001 0.002
0.004 0.000
-0.002 0.001
0.002 -0.002
-0.002 -0.001

Сравнение невязок позволяет сделать вывод, что базовая ГСК — СК-63 зона C0.

Полученные параметры

Изучим содержимое файла key.txt, соответствующего СК-63:

WKT:
A0 = -356718.938772419
A1 = 0.999887380509183
A2 = 0.0161962611321084
B0 = -5719887.1597502
B1 = -0.0161962611321084
B2 = 0.999887380509183

MapInfo:
0.999887380509, 0.016196261132, -356718.93877241889, -0.016196261132, 0.999887380509, -5719887.159750198

Alternate set:
scale = 1.000018546116108
angle = 0.92800077023443683

Мы видим три группы чисел: WKT, MapInfo и Alternate set.

Обратим внимание на параметр разворота angle из третьей группы. Если он мал, в пределах первых десятых долей градуса, имеет смысл отказаться от использования конформного преобразования и вместо этого смещать осевой меридиан для устранения угла разворота.

Создание МСК в MapInfo

Запись базовой СК-63 зона C0 в файле MAPINFOW.PRJ должна выглядеть так:

"CS63 zone C0", 8, 1001, 7, 21.95, 0.1, 1, 250000, 0

Используем вторую группу из файла key.txt. Впишем МСК в MAPINFOW.PRJ как базовую, дополненную аффинным преобразованием:

"Biala Podlaska", 1008, 1001, 7, 21.95, 0.1, 1, 250000, 0, 7, 0.999887380509, 0.016196261132, -356718.93877241889, -0.016196261132, 0.999887380509, -5719887.159750198

Полноценное определение нуждается в параметрах Bounds:

"Biala Podlaska", 3008, 1001, 7, 21.95, 0.1, 1, 250000, 0, 7, 0.999887380509, 0.016196261132, -356718.93877241889, -0.016196261132, 0.999887380509, -5719887.159750198, 52000, 15000, 82000, 45000

Создание МСК в QGIS

Возьмём коэффициенты из первой группы в файле key.txt и создадим МСК в формате WKT как аффинное преобразование на основе проекции "Pulkovo 1942 / CS63 zone C0":

DERIVEDPROJCRS["Biala Podlaska",
    BASEPROJCRS["Pulkovo 1942 / CS63 zone C0",
        BASEGEOGCRS["Pulkovo 1942",
            DATUM["Pulkovo 1942",
                ELLIPSOID["Krassowsky 1940",6378245,298.3,
                    LENGTHUNIT["metre",1]]],
            PRIMEM["Greenwich",0,
                ANGLEUNIT["Degree",0.0174532925199433]]],
        CONVERSION["CS63 zone C0",
            METHOD["Transverse Mercator",
                ID["EPSG",9807]],
            PARAMETER["Latitude of natural origin",0.1,
                ANGLEUNIT["degree",0.0174532925199433],
                ID["EPSG",8801]],
            PARAMETER["Longitude of natural origin",21.95,
                ANGLEUNIT["degree",0.0174532925199433],
                ID["EPSG",8802]],
            PARAMETER["Scale factor at natural origin",1,
                SCALEUNIT["unity",1],
                ID["EPSG",8805]],
            PARAMETER["False easting",250000,
                LENGTHUNIT["metre",1],
                ID["EPSG",8806]],
            PARAMETER["False northing",0,
                LENGTHUNIT["metre",1],
                ID["EPSG",8807]]]],
    DERIVINGCONVERSION["Affine",
        METHOD["Affine parametric transformation",
            ID["EPSG",9624]],
        PARAMETER["A0",-356718.938772649,
            LENGTHUNIT["metre",1],
            ID["EPSG",8623]],
        PARAMETER["A1",0.999887380509304,
            SCALEUNIT["coefficient",1],
            ID["EPSG",8624]],
        PARAMETER["A2",0.0161962611321413,
            SCALEUNIT["coefficient",1],
            ID["EPSG",8625]],
        PARAMETER["B0",-5719887.15975089,
            LENGTHUNIT["metre",1],
            ID["EPSG",8639]],
        PARAMETER["B1",-0.0161962611321413,
            SCALEUNIT["coefficient",1],
            ID["EPSG",8640]],
        PARAMETER["B2",0.999887380509304,
            SCALEUNIT["coefficient",1],
            ID["EPSG",8641]]],
    CS[Cartesian,2],
        AXIS["easting (X)",east,
            ORDER[1],
            LENGTHUNIT["metre",1]],
        AXIS["northing (Y)",north,
            ORDER[2],
            LENGTHUNIT["metre",1]],
    USAGE[
        SCOPE["unknown"],
        AREA["Europe - Poland - Biala Podlaska"],
        BBOX[51.9,22.9,52.1,23.3]]]

В первой строке записали название системы координат латиницей "Biala Podlaska". В конце вставили название покрываемой территории "Europe - Poland - Biala Podlaska" и охват в формате φmin, λmin, φmax, λmax.

Создание МСК в Global Mapper

Третья группа параметров в файле key.txt содержит масштабный коэффициент scale и угол поворота angle. Угол вставляем как есть, а масштабный коэффициент умножим на соответствующий параметр базовой СК.

Заключение

Задачи построения МСК для QGIS и MapInfo выполнены, цель достигнута.

Приложение. Утилита findkey

Программа findkey вычисляет параметры конформного преобразования. Написана на языке C. Вот листинг:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define SEP ';' /* var-file column separator */

/* --------------------------------------------------------------------------
 * findkey
 *
 * Program to compute Helmert 2D transformation parameters
 *
 * Usage: findkey <coord1> <coord2>
 *
 * Input files: coord1 coord2
 *     coord1 - source coordinate 'x1 y1'
 *     coord2 - destination coordinate 'x2 y2'
 *              a row per a point; 3+ points
 *
 * Output files:
 *    key.txt - transformation parameters
 *    var.csv - SEP separated residuals 'dx dy'
 * -------------------------------------------------------------------------- */
int main(int argc, char *argv[])
{
  char buf0[1024], buf1[1024];
  double x[2], y[2];
  double xc[2], yc[2];
  double dx[2], dy[2];
  double s[7] = {0., 0., 0., 0., 0.};
  double det, h[6];
  double mu, theta;
  double yh[2];
  int i;
  FILE *fp0, *fp1, *fp2;

  if (argc < 3) {
    printf("Usage: findkey <coord1> <coord2>\n");
    exit(EXIT_FAILURE);
  }

  if ((fp0 = fopen(argv[1], "r")) == NULL) {
    printf("can't open %s\n", argv[1]);
    exit(EXIT_FAILURE);
  }

  if ((fp1 = fopen(argv[2], "r")) == NULL) {
    printf("can't open %s\n", argv[2]);
    exit(EXIT_FAILURE);
  }

  /* coordinate sums */
  while (fgets(buf0, 1024, fp0) != NULL && fgets(buf1, 1024, fp1) != NULL) {
    sscanf(buf0, "%lf %lf", &x[0], &x[1]);
    sscanf(buf1, "%lf %lf", &y[0], &y[1]);
    s[0] += x[0];
    s[1] += x[1];
    s[2] += y[0];
    s[3] += y[1];
    s[4] += 1.;
  }
  rewind(fp0);
  rewind(fp1);

  /* centrum gravitatis */
  for (i = 0; i < 2; i++) {
    xc[i] = s[i] / s[4];
    yc[i] = s[2 + i] / s[4];
  }

  /* sums of products */
  for (i = 0; i < 7; i++)
    s[i] = 0.;
  while (fgets(buf0, 1024, fp0) != NULL && fgets(buf1, 1024, fp1) != NULL) {
    sscanf(buf0, "%lf %lf", &x[0], &x[1]);
    sscanf(buf1, "%lf %lf", &y[0], &y[1]);
    /* coordinate differences */
    dx[0] = x[0] - xc[0];
    dx[1] = x[1] - xc[1];
    dy[0] = y[0] - yc[0];
    dy[1] = y[1] - yc[1];
    /* summation */
    s[0] += dx[0] * dx[0];
    /*s[1] += dx[0] * dx[1];*/
    s[2] += dx[1] * dx[1];
    s[3] += dx[0] * dy[0];
    s[4] += dx[1] * dy[0];
    s[5] += dx[0] * dy[1];
    s[6] += dx[1] * dy[1];
  }
  rewind(fp0);
  rewind(fp1);

  /* Helmert parameters */
  det = s[0] + s[2];
  h[0] = (s[3] + s[6]) / det;
  h[1] = (s[4] - s[5]) / det;
  h[2] = yc[0] - h[0] * xc[0] - h[1] * xc[1];
  h[3] = -h[1];
  h[4] = h[0];
  h[5] = yc[1] - h[3] * xc[0] - h[4] * xc[1];

  /* alternative Helmert parameter set */
  mu = hypot(h[0], h[1]);
  theta = atan2(h[1], h[0]) / M_PI * 180.;

  /* output parameters */
  if ((fp2 = fopen("key.txt", "w")) == NULL) {
    printf("can't create %s\n", "key.txt");
    exit(EXIT_FAILURE);
  }
  fprintf(fp2, "WKT:\nA0 = %.15g\nA1 = %.15g\nA2 = %.15g\nB0 = %.15g\nB1 = %.15g\nB2 = %.15g\n",
	  h[2], h[0], h[1], h[5], h[3], h[4]);
  fprintf(fp2, "\nMapInfo:\n%.12f, %.12f, %.17g, %.12f, %.12f, %.17g\n",
	  h[0], h[1], h[2], h[3], h[4], h[5]);
  fprintf(fp2, "\nAlternate set:\nscale = %.17g\nangle = %.17g\n", mu, theta);
  fclose(fp2);

  /* output residuals */
  if ((fp2 = fopen("var.csv", "w")) == NULL) {
    printf("can't create %s\n", "var.csv");
    exit(EXIT_FAILURE);
  }
  while (fgets(buf0, 1024, fp0) != NULL && fgets(buf1, 1024, fp1) != NULL) {
    sscanf(buf0, "%lf %lf", &x[0], &x[1]);
    sscanf(buf1, "%lf %lf", &y[0], &y[1]);
    /* model y */
    yh[0] = h[0] * x[0] + h[1] * x[1] + h[2];
    yh[1] = h[3] * x[0] + h[4] * x[1] + h[5];
    fprintf(fp2, "%.15g%c%.15g\n", yh[0] - y[0], SEP, yh[1] - y[1]);
  }
  fclose(fp2);
  fclose(fp1);
  fclose(fp0);

  exit(EXIT_SUCCESS);
}

Сохраним код в файл findkey.c. Создадим исполняемый модуль компилятором gcc:

$ gcc -o findkey findkey.c -lm

Пользователи MS Windows могут загрузить уже скомпилированную программу.

Ссылки