Raphaël Overlay

10 Сентябрь 2010

После недавних экспериментов я решил создать что-то, чем действительно можно пользоваться для рисования по картам.

Ссылка для скачивания в самом низу страницы.

Начал с примитивов — прямоугольников (картинок) и кругов (эллипсов). Дело совершенно не хитрое перевел координату в точки по карте и умножил сторону (радиус) на два в степени приближение.



// рисуем круг из центра координат с радиусом

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. Вот и все, надеюсь будет хоть кому-то полезно!

  1. 10 Сентябрь 2010 в 10:24 | #1

    чертов красавец! так держать! жду надписи «CTAPbIu_MABP» через свг путь!! :))

  2. 10 Сентябрь 2010 в 10:25 | #2

    ахуенчег!

  3. 10 Сентябрь 2010 в 10:29 | #3

    пасиб, чувак
    пойду поищу генератор шрифтов ;)

  4. 10 Сентябрь 2010 в 10:30 | #4

    реальни ахуенчег, особенно после того когда я понял в чем проблема, молор!)

  5. 10 Сентябрь 2010 в 10:31 | #5

    ты б текст помог вычитать и ошибки в коде найти)))

  6. 10 Сентябрь 2010 в 10:32 | #6

    я на работе, а букав многа :( сори в данный момент ни магу, и так скоро пизды дадут)

  7. 10 Сентябрь 2010 в 10:33 | #7

    ну начинай мазать вазилин тогда ;)

  8. mario
    10 Сентябрь 2010 в 10:35 | #8

    Круто! Как раз мне необходимо будет в моем будущем проектике ))) Спасибо! )

  9. 16 Ноябрь 2010 в 21:50 | #9

    Пофиксил баг с логером в опере.
    По какой-то странной причине в опере отображаются не все элементы типа PATH

Комментирование отключено.