Raphaël Overlay
После недавних экспериментов я решил создать что-то, чем действительно можно пользоваться для рисования по картам.
Ссылка для скачивания в самом низу страницы.
Начал с примитивов - прямоугольников (картинок) и кругов (эллипсов). Дело совершенно не хитрое перевел координату в точки по карте и умножил сторону (радиус) на два в степени приближение.
// рисуем круг из центра координат с радиусом
Overlay.prototype.onAdd = function() {
var center = this.getProjection().fromLatLngToDivPixel(new google.maps.LatLng(0,0)), // центр мира
worldWidth = this.getProjection().getWorldWidth(); // длина мира
this.div = document.createElement('div'); // наш слой
this.div.style.border = 'none';
this.div.style.position = 'absolute';
this.div.style.overflow = 'visible';
this.div.style.left = center.x - worldWidth / 2 + 'px'; // от левого края карты
this.div.style.top = center.y - worldWidth / 2 + 'px'; // от верха карты
this.div.style.width = worldWidth+'px'; // шириной в карту
this.div.style.height = worldWidth+'px'; // высотой в карту
// таким образом див полностью перекрывает одну (центральную) секцию карты
// заметте, карта повторяеться горизонтально
// но этот слой лежит только поверх одной секции не перекрывая соседние
this.getPanes().overlayImage.appendChild(this.div); // добавляем слой на карту
this.canvas = Raphael(this.div); // делаем из слоя холст для рисования
};
Overlay.prototype.draw = function() {
var center = this.getProjection().fromLatLngToDivPixel(new google.maps.LatLng(0,0)), // центр круга
worldWidth = this.getProjection().getWorldWidth(), // длина карты
left = center.x - worldWidth / 2, // край карты
top = center.y - worldWidth / 2, // край карты
scale = 1 << this.getMap().getZoom(), // увеличение ( Math.pow(2, zoom))
r = scale * radius, // реальный радиус
x = center.x - left, // координата по слою
y = center.y - top; // координата по слою
this.canvas.clear(); // чистим холст от старых картинок
this.canvas.circle(x, y, r); // рисуем круг
}
Вот собственно простейший пример и готов. Но мне же этого мало! А что если круг нарисовать с радиусом больше чем worldWidth/2? Тогда он будет вылазить за рамки слоя и будет обрезан по краям. Но это же не кошерно (или не по фен-шую :) ). Что же делать? Надо расширить слой. Слой расширяется в четырех направлениях так как он выпирает за границы слоя со всех четырех сторон. просто расширить слой не достаточно потому как див перестанет выпирать только справа и снизу, надо еще сместить див влево и вверх, но если сместили див надо поправить координаты круга. Для тех кто не дружит с математикой как я все страшно запутано поэтому показываю на примере.
// r > worldWidth / 2 по условию
Overlay.prototype.draw = function() {
var center = this.getProjection().fromLatLngToDivPixel(new google.maps.LatLng(0,0)), // центр круга
worldWidth = this.getProjection().getWorldWidth(), // длина карты
left = center.x - worldWidth / 2, // край карты
top = center.y - worldWidth / 2, // край карты
scale = 1 << this.getMap().getZoom(), // увеличение ( Math.pow(2, zoom))
r = scale * radius, // реальный радиус
x = center.x - left, // координата по слою
y = center.y - top, // координата по слою
// отступы
offsetLeft = r - x,
offsetTop = r - y,
offsetRight = r + x - worldWidth,
offsetBottom = r + y - worldWidth,
// длина и высота учитывая отступы
fullWorkdWidth = offsetLeft + worldWidth + offsetRight,
fullWorkdHeight = offsetTop + worldWidth + offsetBottom;
// изменяем положение слоя и размеры холста
this.div.style.left = left - offsetLeft + 'px';
this.div.style.top = top - offsetTop + 'px';
this.div.style.width = fullWorkdWidth + 'px';
this.div.style.height = fullWorkdHeight + 'px';
this.canvas.setSize(fullWorkdWidth, fullWorkdHeight);
this.canvas.clear();
this.canvas.circle(x, y, r); // рисуем круг
}
В результате получаеться где-то такая фигура
Примерно так же рассчитываться прямоугольники, но показывать их просто так не интересно, интересно станет когда фигур на карте будет больше одной и выпирать они будут за края карты с разных сторон и на разную длину. В этом случаи надо сначала просчитать все фигуры, найти максимальные отступы, подвинуть слой, сместить фигуры и только потом что-то рисовать. Пробуем:
Походу с примитивами разобрались, Но кому нужны эти примитивы? Все хотят отмечать на карте разного рода кривые и полигоны (многогранники), что же, нужно и это реализовать. Возьмем пример которым пользуется Dmitry Baranovskiy, автор RaphaelJS - это петля Мёбиуса, знак бесконечности или просто перевернутая восьмерка для людей без воображения. Задаётся она вот такими координатами M100,100c0,50 100-50 100,0c0,50 -100-50 -100,0z. Координаты как вы ведите относительные (это видно из маленьких "с"), но даже если бы они были абсолютными это было бы не страшно. Первое что делаем переводим абсолютные в относительные, потом смещаем первую точку куда нам надо и умножаем все остальные точки по известной формуле.
Overlay.prototype.draw = function() {
var center = this.getProjection().fromLatLngToDivPixel(new google.maps.LatLng(0,0)), // точка смещения
worldWidth = this.getProjection().getWorldWidth(), // длина карты
left = center.x - worldWidth / 2, // край карты
top = center.y - worldWidth / 2, // край карты
scale = 1 << this.getMap().getZoom(), // увеличение ( Math.pow(2, zoom))
path = Raphael.pathToRelative(Raphael.parsePathString("M100,100c0,50 100-50 100,0c0,50 -100-50 -100,0z"));
line = [];
for (var i in path) {
for (var j in path[i]) {
if (j == 0) {
line[i] = [path[i][j]]; // буква
} else if (i==0 && j==1) {
line[i].push(center.x - left + path[i][j]); // начальная точка по X
} else if (i==0 && j==2) {
line[i].push(center.y - top + path[i][j]); // начальная точка по Y
} else {
line[i].push(path[i][j] * scale); // все остальные точки
}
}
}
this.canvas.clear();
this.canvas.path(line);
}
Единственный минус рисования кривой (как и текста), это то что пока не нарисуешь не узнаешь размеров, конечно можно долго высчитывать их по координатам но по моему это того не стоит, особенно если координаты относительные. С другой стороны узнать размер можно после рисования, вытереть и сместить все что нужно и нарисовать по новой, но по моему это требует еще больше усилий, хотя формулой для этого я не поделиться не могу.
// для кривой (рисуеться с левого верхнего края)
var path = this.canvas.path(line),
width = path.getBBox().width,
height = path.getBBox().height,
x = point.x - left,
y = point.y - top;
// для текста (рисуеться из центра)
var text = this.canvas.text(point.x, point.y, text),
width = text.getBBox().width,
height = text.getBBox().height,
x = point.x - width / 2 - left,
y = point.y - height / 2 - top;
С основными возможностями и трудностями рисования разобрались переходим к конкретным примерам
Прямоугольники
Есть пять разный вариантов нарисовать прямоугольник
- Ограничить область на карте с помощью границ (LatLngBounds)
- Нарисовать как во всех графических редакторах из верхнего левого угла с привязкой к координате
- Нарисовать как круг указав координату центра
- Нарисовать от левого верхнего угла в определенной точке на карте, при этом надо знать размер дива с картой и сам прямоугольник не будет менять позиции про движении карты, а уедет за край видимой области
- То же что и предыдущий, только из центра
var map = new google.maps.Map(document.getElementById("map"), {
zoom: 0,
center: new google.maps.LatLng(0,0),
mapTypeId: google.maps.MapTypeId.SATELLITE
});
new RaphaelOverlay({
map:map,
shapes : [{
// прямоугольник ограниченный парой координат
// может пропадать при движении карты
// из-за отрицательной длинны
position : new google.maps.LatLngBounds(
// верхний првым угол (north-east)
new google.maps.LatLng(-50, -100),
// нижний левый угол (south-west)
new google.maps.LatLng(50, 100)
),
type: "rect",
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#00f',
"fill-opacity": 0.3
}
},{
// обычный прямоугольник нарисованный
// из верхнего левого угла
// от координат
position : new google.maps.LatLng(85, -175),
// и имеющий размеры 100x100
size : new google.maps.Size(100, 100),
type: "rect",
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#0f0',
"fill-opacity": 0.3
}
},{
// прямоугольник нарисованный
// из центра
// от координат
center : new google.maps.LatLng(-85, 175),
// и имеющий размеры 100x100
size : new google.maps.Size(100, 100),
type: "rect",
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#f00',
"fill-opacity": 0.3
}
},{
// прямоугольник нарисованный
// из верхнего левого угла
// от верхнего левого угла карты
// такой прямоугольник не меняет позиции
// при движении карты
position : new google.maps.Point(300, 100),
size : new google.maps.Size(100, 100),
type: "rect",
// скругление углов
radius: 10,
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#ff0',
"fill-opacity": 0.3
}
},{
// такой же как предыдущий, только
// из центра
center : new google.maps.Point(300, 100),
size : new google.maps.Size(100, 100),
type: "rect",
// скругление углов
radius: 10,
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#f0f',
"fill-opacity": 0.3
}
}]
});
Картинки
По большому счету ничем не отличаются от прямоугольников, кроме того что имеют ссылку на картинку и не имеют скругления углов
new RaphaelOverlay({
map:map,
shapes : [{
// ссылка на картинку
src : "http://upload.wikimedia.org/wikipedia/commons/d/d6/Wikipedia-logo-v2-en.png",
position : new google.maps.LatLng(85, -175),
size : new google.maps.Size(135, 155),
type: "image"
}]
});
Круги и овалы
new RaphaelOverlay({
map:map,
shapes : [{
// у круга центр совпадает с позицией
// но центр приоритетнее
center: new google.maps.LatLng(85, 175),
type: "circle",
radius: 100,
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#0f0',
"fill-opacity": 0.3
}
},{
// как и квадрат круг и овал можно рисовать
// из фиксированной точки
position: new google.maps.Point(300, 100),
type: "circle",
radius: 100,
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#f00',
"fill-opacity": 0.3
}
},{
// овал отличается от круга двумя радиусами
position: new google.maps.LatLng(-85, -175),
type: "ellipse",
rx: 100,
ry: 50,
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#00f',
"fill-opacity": 0.3
}
}]
});
Кривые и полигоны
new RaphaelOverlay({
map:map,
shapes : [{
// Фигура нарисованная по координатам
// может менять ширину при движении карты
position : [
new google.maps.LatLng(84, -175),
new google.maps.LatLng(84, 175),
new google.maps.LatLng(0, 0)
],
type: "polygon",
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#f00',
"fill-opacity": 0.3
}
},{
// Фигура нарисованная по кривой
// с абсолютными координатами
position: new google.maps.LatLng(85, -175),
path: "M 0 0 L 200 0 L 100 200 z",
type: "path",
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#f00',
"fill-opacity": 0.3
}
},{
// Фигура нарисованная по кривой
// с относительными координатами
position: new google.maps.LatLng(85, -175),
path: "M100,100c0,50 100-50 100,0c0,50 -100-50 -100,0z",
type: "path",
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#f00',
"fill-opacity": 0.3
}
}]
});
Текст
new RaphaelOverlay({
map:map,
shapes : [{
position: new google.maps.LatLng(0, 0),
text : "CTAPbIu_MABP",
type: "text",
attr : {
fill: '#fff',
"font-size" : 20
}
}]
});
Похоже это все, забыл только сказать что есть еще настройка приближения. Поскольку во всех примерах приближение поставлено минимальное, то при приближении хотя бы 10 все фигуры распадутся и будет очень некрасиво. Это происходит по разным причинам - кривизна SVG или VML или переполнение int32 в параметре ширины или высоты. Поэтому я сделал поправку на приближение. Первых два пункта интуитивно понятны это минимальное и максимальное приближение на котором видно фигуру, а третий пункт это приближение на котором заданы координаты кривых. То есть фигуру видно с 3 по 5 приближение, но координаты были рассчитаны при нулевом то можно это указать, иначе считается что они были заданы по меньшему из видимых. Смотрите пример с 3 по 5 приближение возле маркера.
new RaphaelOverlay({
map:map,
shapes : [{
position: new google.maps.LatLng(0, 0),
path: "M0,0c0,50 100-50 100,0c0,50 -100-50 -100,0z",
type: "path",
zoom : {
min : 3,
max : 5,
},
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#f00',
"fill-opacity": 0.3
}
},{
position: new google.maps.LatLng(0, 0),
path: "M0,0c0,50 100-50 100,0c0,50 -100-50 -100,0z",
type: "path",
zoom : {
min : 3,
max : 5,
adjusted : 0
},
attr : {
stroke: '#fff',
"stroke-width":2,
fill: '#f00',
"fill-opacity": 0.3
}
}]
});
Скачать скрипт можно тут DOWNLOAD LINK. Вот и все, надеюсь будет хоть кому-то полезно!
Свежие комментарии