Карта мира с произвольным центральным меридианом в MapInfo: различия между версиями
ErnieBoyd (обсуждение | вклад) |
ErnieBoyd (обсуждение | вклад) |
||
Строка 58: | Строка 58: | ||
=== Перемещение половины слоя на 360° === | === Перемещение половины слоя на 360° === | ||
Чтобы объекты из файла '''GSHHS_c_L1_1.MIF''' оказались на своих местах на будущей карте, их необходимо перенести на 360° к западу. Вот листинг программы, которая смещает все объекты файла MIF/MID на заданные приращения координат ∆''X'' и ∆''Y'': | |||
<syntaxhighlight lang="c"> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#define NKEYS 12 | |||
#define STRMAX 1024 | |||
/* ========================================================================== * | |||
* shiftxy | |||
* | |||
* Program to read objects from one MIF file, | |||
* shift them dx, dy map units, and write to another MIF file. | |||
* | |||
* Usage: shiftxy <input> <output> <dx> <dy> | |||
* | |||
* No actions on MID file are carried out, | |||
* just use original one copying or renaming. | |||
* -------------------------------------------------------------------------- */ | |||
int main(int argc, char *argv[]) | |||
{ | |||
char buf[STRMAX], w[8][128]; | |||
char *keyword[NKEYS] = { | |||
"Arc", "Ellipse", "Line", "Pline", "Point", "Region", "Rect", | |||
"Roundrect", "Text", "Multipoint", "Center", "Multiple" | |||
}; | |||
double dx, dy, x, y, x2, y2; | |||
int nsec, npt, t, k; | |||
FILE *fp0, *fp1; | |||
if (argc < 5) { | |||
puts("usage: shiftxy <input> <output> <dx> <dy>"); | |||
exit(EXIT_SUCCESS); | |||
} | |||
if ((fp0 = fopen(argv[1], "r")) == NULL) { | |||
fprintf(stderr, "can't open %s\n", argv[1]); | |||
exit(EXIT_FAILURE); | |||
} | |||
if ((fp1 = fopen(argv[2], "w")) == NULL) { | |||
fprintf(stderr, "can't create %s\n", argv[2]); | |||
exit(EXIT_FAILURE); | |||
} | |||
dx = atoi(argv[3]); | |||
dy = atoi(argv[4]); | |||
nsec = npt = t = 0; | |||
while (fgets(buf, STRMAX, fp0) != NULL) { | |||
if (npt == 0) { | |||
if (nsec == 0) { | |||
switch (t) { | |||
case 2: /* Text string */ | |||
fputs(buf, fp1); | |||
t = 1; | |||
break; | |||
case 1: /* Text bbox */ | |||
sscanf(buf, "%s %s %s %s", w[0], w[1], w[2], w[3]); | |||
x = atof(w[0]) + dx; | |||
y = atof(w[1]) + dy; | |||
x2 = atof(w[2]) + dx; | |||
y2 = atof(w[3]) + dy; | |||
fprintf(fp1, " %f %f %f %f\n", x, y, x2, y2); | |||
t = 0; | |||
break; | |||
default: | |||
sscanf(buf, "%s %s %s %s %s %s %s", | |||
w[0], w[1], w[2], w[3], w[4], w[5], w[6]); | |||
for (k = 0; k < NKEYS - 1; k++) { | |||
if (strcmp(w[0], keyword[k]) == 0) | |||
break; | |||
} | |||
switch (k) { | |||
case 8: /* "Text" */ | |||
t = 2; | |||
fputs(buf, fp1); | |||
break; | |||
case 9: /* "Multipoint" */ | |||
npt = atoi(w[1]); | |||
fputs(buf, fp1); | |||
break; | |||
case 5: /* "Region" */ | |||
nsec = atoi(w[1]); | |||
fputs(buf, fp1); | |||
break; | |||
case 3: /* "Pline" */ | |||
if (strcmp(w[1], keyword[NKEYS - 1]) == 0) /* "Pline Multiple" */ | |||
nsec = atoi(w[2]); | |||
else | |||
npt = atoi(w[1]); | |||
fputs(buf, fp1); | |||
break; | |||
case 4: /* "Point" */ | |||
x = atof(w[1]) + dx; | |||
y = atof(w[2]) + dy; | |||
fprintf(fp1, "%s %f %f\n", w[0], x, y); | |||
break; | |||
case 10: /* "Center" */ | |||
x = atof(w[1]) + dx; | |||
y = atof(w[2]) + dy; | |||
fprintf(fp1, " %s %f %f\n", w[0], x, y); | |||
break; | |||
case 1: /* "Ellipse" */ | |||
case 2: /* "Line" */ | |||
case 6: /* "Rect" */ | |||
x = atof(w[1]) + dx; | |||
y = atof(w[2]) + dy; | |||
x2 = atof(w[3]) + dx; | |||
y2 = atof(w[4]) + dy; | |||
fprintf(fp1, "%s %f %f %f %f\n", | |||
w[0], x, y, x2, y2); | |||
break; | |||
case 7: /* "Roundrect" */ | |||
x = atof(w[1]) + dx; | |||
y = atof(w[2]) + dy; | |||
x2 = atof(w[3]) + dx; | |||
y2 = atof(w[4]) + dy; | |||
fprintf(fp1, "%s %f %f %f %f %s\n", | |||
w[0], x, y, x2, y2, w[5]); | |||
break; | |||
case 0: /* "Arc" */ | |||
x = atof(w[1]) + dx; | |||
y = atof(w[2]) + dy; | |||
x2 = atof(w[3]) + dx; | |||
y2 = atof(w[4]) + dy; | |||
fprintf(fp1, "%s %f %f %f %f %s %s\n", | |||
w[0], x, y, x2, y2, w[5], w[6]); | |||
break; | |||
default: | |||
fputs(buf, fp1); | |||
} | |||
} | |||
} else { | |||
npt = atoi(buf); | |||
fputs(buf, fp1); | |||
nsec--; | |||
} | |||
} else { | |||
sscanf(buf, "%s %s", w[0], w[1]); | |||
x = atof(w[0]) + dx; | |||
y = atof(w[1]) + dy; | |||
fprintf(fp1, "%f %f\n", x, y); | |||
npt--; | |||
} | |||
} | |||
fclose(fp1); | |||
fclose(fp0); | |||
return 0; | |||
} | |||
/* ========================================================================== */ | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="bash"> | |||
$ gcc -o shiftxy shiftxy.c | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="bash"> | |||
$ shiftxy GSHHS_c_L1_1.MIF GSHHS_c_L1_2.MIF -360 0 | |||
</syntaxhighlight> | |||
=== Воссоединение слоя === | === Воссоединение слоя === | ||
== Заключение == | == Заключение == |
Версия от 15:41, 24 марта 2013
MapInfo при отображении карты в проекции не сворачивает слои вокруг меридиана-антипода. Задача подготовки слоёв для отображения ложится на плечи картографа.
Введение
Как многие ГИС, MapInfo при отображении карты в какой-либо проекции не сворачивает отображаемые слои вокруг меридиана-антипода, который отстоит на 180° от центрального. Это позволяет создавать карты мира, разрезанные по произвольной линии, — линии смены дат, например. Однако для пользователя свобода, как обычно, идёт рука об руку с необходимостью. Если имеющаяся в наличии карта должна быть отображена в проекции, центральный меридиан которой отличается от среднего меридиана исходной карты, в форме милой глазу симметричной фигуры, то картографу для этого придётся приложить некоторые усилия.
Данные
В качестве тестового материала используем карту мира GSHHG, которая распространяется под лицензией LGPL. Эта карта развивается как географическая основа открытого проекта GMT. GMT сворачивает изображение вокруг меридиана-антипода, и слои карты в «родном» формате не содержат разрезанных объектов. Однако перед экспортом слоёв в формат ESRI shapefiles полигоны под меридианом 180° разрезаются на восточную и западную часть, что даёт карту в стандартном диапазоне долгот ±180°.
Постановка задачи
Для демонстрации возьмём из GSHHG несколько слоёв грубого (crude) разрешения. Поставим перед собой цель отобразить карту в проекции Робинсона с центральным меридианом 150° з. д. Для достижения этой цели создадим новые слои в диапазоне долгот от 330° з. д. до 30° в. д. Кроме того, дополним карту слоями сетки параллелей и меридианов grid15 и «океана» ocean.
Построение карты
Программа PacWorld
Утилита PacWorld от IAA Pty Ltd решает задачу преобразования карты из стандартного диапазона долгот в диапазон 0°–360°. Поскольку она доступна в кодах MapBasic, можно модифицировать её для работы с произвольным центральным меридианом.
Алгоритм работы начинается с создания полигона, одной из сторон которого является начальный меридиан. Объекты слоя разрезается этим полигоном. Затем объекты западного полушария модифицируются: каждый узел перемещается на 360° к востоку.
Недостатки PacWorld, помимо упомянутой жёсткой привязки к меридиану 180° в. д.:
- разрезы материков на краю карты отображаются в проекциях несглаженными прямыми отрезками; — легко исправить, запрограммировав создание промежуточных узлов в стороне полигона, образованной начальным меридианом;
- некорректно трансформируется Антарктида; — можно обойти эту проблему через конвертирование полигона в полилинию и обратно с некоторым редактированием;
- зависание при переносе объектов с большим количеством узлов; с картой GSHHG это происходит уже для слоя континентов со средним (intermediate) разрешением; похоже, дело в принципиальной ограниченности ресурсов, выделяемых программам MapBasic; — фатальный недостаток.
Проблема с зависанием связана не с процедурой разрезания. Зависание происходит в процессе перемещения узлов. Можно модифицировать PacWorld так, чтобы он только разрезал объекты, а перемещение осуществить другими средствами. Однако с методической точки зрения полезно поработать руками.
Конструирование координатной сетки и «океана»
Построим слой параллелей и меридианов grid15 программой GridMaker, входящей в набор стандартных утилит MapInfo. Используем четыре линии по контуру для создания слоя «океана» ocean.
Разрезание слоя
Разрежем слой континентов и островов GSHHS_c_L1 на две половины полигоном слоя ocean. Часть, попадающую на полигон (и, следовательно, на будущую карту), сохраним как новый слой с прежним именем GSHHS_c_L1 в другую папку. Часть, не попадающую на полигон, экспортируем в файл формата MIF/MID под именем GSHHS_c_L1_1.MIF.
Перемещение половины слоя на 360°
Чтобы объекты из файла GSHHS_c_L1_1.MIF оказались на своих местах на будущей карте, их необходимо перенести на 360° к западу. Вот листинг программы, которая смещает все объекты файла MIF/MID на заданные приращения координат ∆X и ∆Y:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NKEYS 12
#define STRMAX 1024
/* ========================================================================== *
* shiftxy
*
* Program to read objects from one MIF file,
* shift them dx, dy map units, and write to another MIF file.
*
* Usage: shiftxy <input> <output> <dx> <dy>
*
* No actions on MID file are carried out,
* just use original one copying or renaming.
* -------------------------------------------------------------------------- */
int main(int argc, char *argv[])
{
char buf[STRMAX], w[8][128];
char *keyword[NKEYS] = {
"Arc", "Ellipse", "Line", "Pline", "Point", "Region", "Rect",
"Roundrect", "Text", "Multipoint", "Center", "Multiple"
};
double dx, dy, x, y, x2, y2;
int nsec, npt, t, k;
FILE *fp0, *fp1;
if (argc < 5) {
puts("usage: shiftxy <input> <output> <dx> <dy>");
exit(EXIT_SUCCESS);
}
if ((fp0 = fopen(argv[1], "r")) == NULL) {
fprintf(stderr, "can't open %s\n", argv[1]);
exit(EXIT_FAILURE);
}
if ((fp1 = fopen(argv[2], "w")) == NULL) {
fprintf(stderr, "can't create %s\n", argv[2]);
exit(EXIT_FAILURE);
}
dx = atoi(argv[3]);
dy = atoi(argv[4]);
nsec = npt = t = 0;
while (fgets(buf, STRMAX, fp0) != NULL) {
if (npt == 0) {
if (nsec == 0) {
switch (t) {
case 2: /* Text string */
fputs(buf, fp1);
t = 1;
break;
case 1: /* Text bbox */
sscanf(buf, "%s %s %s %s", w[0], w[1], w[2], w[3]);
x = atof(w[0]) + dx;
y = atof(w[1]) + dy;
x2 = atof(w[2]) + dx;
y2 = atof(w[3]) + dy;
fprintf(fp1, " %f %f %f %f\n", x, y, x2, y2);
t = 0;
break;
default:
sscanf(buf, "%s %s %s %s %s %s %s",
w[0], w[1], w[2], w[3], w[4], w[5], w[6]);
for (k = 0; k < NKEYS - 1; k++) {
if (strcmp(w[0], keyword[k]) == 0)
break;
}
switch (k) {
case 8: /* "Text" */
t = 2;
fputs(buf, fp1);
break;
case 9: /* "Multipoint" */
npt = atoi(w[1]);
fputs(buf, fp1);
break;
case 5: /* "Region" */
nsec = atoi(w[1]);
fputs(buf, fp1);
break;
case 3: /* "Pline" */
if (strcmp(w[1], keyword[NKEYS - 1]) == 0) /* "Pline Multiple" */
nsec = atoi(w[2]);
else
npt = atoi(w[1]);
fputs(buf, fp1);
break;
case 4: /* "Point" */
x = atof(w[1]) + dx;
y = atof(w[2]) + dy;
fprintf(fp1, "%s %f %f\n", w[0], x, y);
break;
case 10: /* "Center" */
x = atof(w[1]) + dx;
y = atof(w[2]) + dy;
fprintf(fp1, " %s %f %f\n", w[0], x, y);
break;
case 1: /* "Ellipse" */
case 2: /* "Line" */
case 6: /* "Rect" */
x = atof(w[1]) + dx;
y = atof(w[2]) + dy;
x2 = atof(w[3]) + dx;
y2 = atof(w[4]) + dy;
fprintf(fp1, "%s %f %f %f %f\n",
w[0], x, y, x2, y2);
break;
case 7: /* "Roundrect" */
x = atof(w[1]) + dx;
y = atof(w[2]) + dy;
x2 = atof(w[3]) + dx;
y2 = atof(w[4]) + dy;
fprintf(fp1, "%s %f %f %f %f %s\n",
w[0], x, y, x2, y2, w[5]);
break;
case 0: /* "Arc" */
x = atof(w[1]) + dx;
y = atof(w[2]) + dy;
x2 = atof(w[3]) + dx;
y2 = atof(w[4]) + dy;
fprintf(fp1, "%s %f %f %f %f %s %s\n",
w[0], x, y, x2, y2, w[5], w[6]);
break;
default:
fputs(buf, fp1);
}
}
} else {
npt = atoi(buf);
fputs(buf, fp1);
nsec--;
}
} else {
sscanf(buf, "%s %s", w[0], w[1]);
x = atof(w[0]) + dx;
y = atof(w[1]) + dy;
fprintf(fp1, "%f %f\n", x, y);
npt--;
}
}
fclose(fp1);
fclose(fp0);
return 0;
}
/* ========================================================================== */
$ gcc -o shiftxy shiftxy.c
$ shiftxy GSHHS_c_L1_1.MIF GSHHS_c_L1_2.MIF -360 0