Создание пользовательской координатной системы
API предоставляет возможности для создания пользовательских координатных систем и осуществления навигации по картам, "склеенным" вдоль вертикальной и/или горизонтальной оси.
Для того чтобы создать пользовательскую координатную систему, необходимо:
- Задать объект-точку системы, см. Интерфейс YMaps.ICoordPoint.
- Задать объект-область системы, см. Интерфейс YMaps.ICoordBounds .
- Задать правила пересчета координат системы в пикселы на последнем масштабе карты, см. Интерфейс YMaps.ICoordSystem.
Ниже подробно описано создание галактической координатной системы, предназначенной для навигации по астрономической карте нашей Галактики.
Как задать объект-точку
Интерфейс YMaps.ICoordPoint позволяет задать объект-точку пользовательской координатной системы, когда нельзя воспользоваться стандартными классами YMaps.Point и YMaps.GeoPoint.
С помощью YMaps.ICoordPoint необходимо реализовать следующие методы:
- методы получения первой и второй координаты точки - getX() и getY();
- методы задания первой и второй координат точки - setX() и setY();
- метод создания копии объекта - copy();
- методы операций над точками: смещение в точку moveTo(), смещение на вектор и разность точек в виде вектора diff();
- метод сравнения точек equals().
Рассмотрим использование интерфейса на примере карты Млечного Пути:
На астрономической карте Галактики используются специальные координаты: галактическая долгота (изменяется от 360 градусов до 0) и галактическая широта (изменяется от -90 до 90 градусов), поэтому стандартные классы географических точек использовать нельзя.
Кроме того, карта "склеена" вдоль горизонтальной оси, поэтому точки на ней могут, как и в случае с картой Земли, быть ограниченными и неограниченными, см. Преобразование координат. Один градус в этой системе координат приблизительно равен 500 световым годам.
Ограниченные точки в галактических координатах имеют широту от 0 до 360 и долготу от -90 до 90 градусов. Неограниченные точки могут принимать произвольные координаты и, фактически, являются вектором, соединяющим начало системы координат с определенной точкой. Для различения ограниченных и неограниченных точек используется флаг unbounded
.
Чтобы задать объект-точку выполните следующие шаги:
-
Создайте вспомогательные функции для расчета ограниченности точек.
Например:
// Вспомогательная функция: ограничивает значение val сверху и снизу function boundaryRestrict(val, min, max) { return Math.max(Math.min(val, max), min); } // Вспомогательная функция: ограничивает значение val, считая что val изменяется циклически function cycleRestrict(val, min, max) { return val - Math.floor((val - min)/(max - min)) * (max-min); }
-
Создайте класс точки координатной системы.
Например:
// Класс точки галактической координатной системы function MyPoint (x, y, unbounded) { this.unbounded = !!unbounded; this.setX(x); this.setY(y); }
-
Определите методы класса точки.
Например:
MyPoint.prototype = { // Возвращает галактическую долготу getX: function () { return this.x; }, // Возвращает галактическую широту getY: function () { return this.y; }, // Задает долготу. Если точка ограниченная, то долгота должна лежать в диапазоне от 0 до 360 градусов setX: function (x) { this.x = this.unbounded ? x : cycleRestrict(x, 0, 360); return this; }, // Задает широту. Широта всегда лежит в диапазоне от -90 до 90 градусов setY: function (y) { this.y = boundaryRestrict(y, -90, 90); return this; }, // Создает копию copy: function () { return new MyPoint(this.x, this.y, this.unbounded); }, // Передвигает точку, при этом флаг ограниченности сохраняется moveTo: function (point) { this.setX(point.getX()); this.setY(point.getY()); return this; }, // Рассчитывает разность точек в виде вектора diff: function (point) { return new YMaps.Point(point.getX() - this.x, point.getY() - this.y); }, // Сдвигает точку на вектор moveBy: function (vector) { this.setX(this.x + vector.getX()); this.setY(this.y + vector.getY()); return this; }, // Сравнивает две точки equals: function (point) { return (Math.abs(this.getX() - point.getX()) < 1e-8 && Math.abs(this.getY() - point.getY()) < 1e-8); } };
Как задать объект-область
Интерфейс YMaps.ICoordBounds позволяет задавать пользовательские объекты-области координатной системы, когда нельзя воспользоваться стандартными объектами YMaps.Bounds и YMaps.GeoBounds.
С помощью YMaps.ICoordBounds необходимо реализовать следующие методы:
- getTop, getRight, getBottom, getLeft, которые возвращают верхнюю, правую, нижнюю и левую границы области, соответственно;
- getRightTop, getRightBottom, getLeftBottom, getLeftTop, которые возвращают (в виде точки координатной системы) правый верхний, правый нижний, левый нижний и левый верхний углы области, соответственно;
- getCenter и getSpan возвращают центр и размеры области;
- getMapZoom, возвращающий максимальное значение коэффициента масштабирования, при котором область видна на карте целиком;
- equals проверяющий, совпадают ли две области;
- contains проверяющий, содержит ли область переданную точку;
- copy возвращающий копию области.
Например, чтобы задать объект-область на карте Млечного пути, реализуйте класс MyBounds
(см. пример ниже). При этом следует учесть, что точки в галактической системе координат могут быть двух типов (ограниченные и неограниченные), соответственно и область также может принадлежать к одному из двух типов: заданная либо ограниченными, либо неограниченными точками.
// Класс "область на карте"
function MyBounds(leftBottom, rightTop) {
this.left = leftBottom.getX();
this.right = rightTop.getX();
this.bottom = leftBottom.getY();
this.top = rightTop.getY();
// Флаг unbounded указывает какими точками задана область: ограниченными или неограниченными
this.unbounded = leftBottom.unbounded && rightTop.unbounded;
}
MyBounds.prototype = {
// Границы области
getTop: function () {
return this.top;
},
getRight: function () {
return this.right;
},
getBottom: function () {
return this.bottom;
},
getLeft: function () {
return this.left;
},
// Углы области
getRightTop: function () {
return new MyPoint(this.right, this.top, this.unbounded);
},
getRightBottom: function () {
return new MyPoint(this.right, this.bottom, this.unbounded);
},
getLeftBottom: function () {
return new MyPoint(this.left, this.bottom, this.unbounded);
},
getLeftTop: function () {
return new MyPoint(this.left, this.top, this.unbounded);
},
// Центр области
getCenter: function () {
var x = (this.left + this.right) / 2,
y = (this.top + this.bottom) / 2;
// Если координата левой точки меньше координаты правой, то берется диаметрально противоположная точка
if (this.right > this.left && !this.unbounded) {
x += 180;
}
return new MyPoint(x, y, this.unbounded);
},
// Размеры области
getSpan: function () {
return new YMaps.Size(Math.abs(this.left - this.right), Math.abs(this.top - this.bottom));
},
// Вычисляет масштаб карты, при котором область видна целиком
getMapZoom: function (map) {
var pixelLeftBottom = map.coorSystem.toPixels(this.getLeftBottom()),
pixelRightTop = map.coorSystem.toPixels(this.getRightTop()),
// Вычисляет размеры области в пикселах на последнем масштабе
pixelSpan = pixelLeftBottom.diff(pixelRightTop).apply(Math.abs),
// Вычисляет размер HTML-элемента карты в пикселах
mapSize = map.getContainerSize(),
// Вычисляет отношение размеров области и карты
scale = Math.max(pixelSpan.getX() / mapSize.getX(), pixelSpan.getX() / mapSize.getX()),
// Вычисляет количество значений коэффициента масштабирования (не
// превышающих максимального), при которых область видна на карте целиком
offset = scale < 1 ? 0 : Math.ceil(Math.log(scale)/Math.LN2);
// Вычисляет уровень масштаба карты
return map.coordSystem.getMaxZoom() - offset;
},
// Сравнивает области
equals: function (myBounds) {
var precision = 1e-10; // Точность сравнения объектов
return (Math.abs(this.top - myBounds.getTop()) < precision &&
Math.abs(this.right - myBounds.getRight()) < precision &&
Math.abs(this.bottom - myBounds.getBottom()) < precision &&
Math.abs(this.left - myBounds.getLeft()) < precision);
},
// Копирует область
copy: function () {
return new MyBounds(this.getLeftBottom(), this.getRightTop());
},
// Возвращает true, если точка попадает в область и false - если нет
contains: function (point) {
// Если область задана неограниченными точками, пересчитывает координаты так, чтобы координаты левой границы всегда были больше координат правой
var left = (this.left < this.right && !this.unbounded) ? this.left + 360 : this.right,
right = this.right,
// Аналогично, если точка неограниченная, пересчитывает ее координаты так, чтобы долгота была больше правой границы области
x = (point.getX() < this.right && !point.unbounded) ? point.getX() + 360 : point.getX(),
y = point.getY();
return (x <= left && x >= right && y >= this.bottom && y <= this.top);
}
}
Как задать правила пересчета координат
Интерфейс YMaps.ICoordSystem позволяет задавать пользовательские правила пересчета координат системы в пиксельные координаты на последнем масштабе карты.
С помощью YMaps.ICoordSystem необходимо реализовать следующие методы:
- Фабричные методы создания объекта-точки и объекта-области координатной системы: методы getCoordPoint и getCoordBounds.
- Правила пересчета координат в пикселы на последнем масштабе и обратно: методы fromCoordPoint и toCoordPoint. Правила пересчета позволяют сопоставить каждой точке пользовательской системы координат определенную точку на карте и наоборот.
- Правила вычисления расстояния между точками (минимального и по линейке): методы distance и rulerDistance.
- Методы вычисления размера и максимального масштаба в пользовательской координатной системе: getWorldSize и getMaxZoom.
- Метод ограничения границ области, в которой объекты могут находиться на карте restrict.
Например:
function MyCoordSystem () {
// Размеры мира на нулевом уровне масштаба
this.worldSize = new YMaps.Point(32768, 1024);
// Ограничение по широте для объектов на карте
this.yRestriction = 5;
// Вычисляет длину одного градуса широты и долготы в пикселах на последнем масштабе
this.scale = this.worldSize.copy().scale(new YMaps.Point(1 / 360, 1 / (2 * this.yRestriction)));
// Длина одного градуса дуги в световых годах
this.lightYearsPerDegree = 500;
};
MyCoordSystem.prototype = {
// Возвращает объект-точку
getCoordPoint: function (x, y, unbounded) {
return new MyPoint(x, y, unbounded);
},
// Возвращает объект-область
getCoordBounds: function (leftBottom, rightTop) {
return new MyBounds(leftBottom, rightTop);
},
// Преобразует координаты точки, заданные в пользовательской координатной системе в пиксельные координаты
fromCoordPoint: function (point, anchor) {
var x,
y = point.getY();
// Если точка ограниченная и задан anchor, сдвигает точку как можно ближе к anchor
if (!point.unbounded && anchor) {
x = cycleRestrict(point.getX(), anchor.getX() - 180, anchor.getX() + 180);
} else {
x = point.getX();
}
return new YMaps.Point(
this.worldSize.getX() - x * this.scale.getX(),
this.worldSize.getY() / 2 - y * this.scale.getY()
);
},
// Преобразует пиксельные координаты точки в координаты, заданные в пользовательской координатной системе
toCoordPoint: function (pixelPoint, unbounded) {
var x = (this.worldSize.getX() - pixelPoint.getX()) / this.scale.getX(),
y = (this.worldSize.getY() / 2 - pixelPoint.getY()) / this.scale.getY();
return this.getCoordPoint(x, y, unbounded);
},
// Задает ограничения для отображения объектов на карте
restrict: function (point) {
return point.copy().setY(boundaryRestrict(point.getY(), - this.yRestriction, this.yRestriction));
},
// Возвращает кратчайшее расстояние между двумя точками координатной системы
distance: function (point1, point2) {
var dx = point1.getX() - point2.getX(),
dy = point1.getY() - point2.getY();
return this.lightYearsPerDegree * Math.sqrt(dx * dx + dy * dy);
},
// Возвращает максимальный коэффициент масштабирования
getMaxZoom: function () {
return 2;
},
// Возвращает размеры мира в пикселах при максимальном масштабе
getWorldSize: function () {
return this.worldSize.copy();
}
};
MyCoordSystem.prototype.rulerDistance = MyCoordSystem.prototype.distance;