В примере показано, как на основе класса линии Polyline создать модуль для рисования
стрелок на карте.
В примере создаются два пользовательских модуля - один отвечает за класс объекта-стрелки, второй модуль
включает в себя визуальное отображение стрелки - класс, реализующий интерфейс IOverlay.
В примере модули располагаются в одном файле. При разработке проекта рекомендуется помещать модули в отдельные файлы.
\n\n","arrow.js":"ymaps.ready(function () {\n var myMap = new ymaps.Map('map', {\n center: [55.733835, 37.588227],\n zoom: 5\n }, {\n searchControlProvider: 'yandex#search'\n });\n // Пользовательские модули не дописываются в неймспейс ymaps.\n // Поэтому доступ к ним мы можем получить асинхронно через метод ymaps.modules.require.\n ymaps.modules.require(['geoObject.Arrow'], function (Arrow) {\n var arrow = new Arrow([[57.733835, 38.788227], [55.833835, 35.688227]], null, {\n geodesic: true,\n strokeWidth: 5,\n opacity: 0.5,\n strokeStyle: 'shortdash'\n });\n myMap.geoObjects.add(arrow);\n });\n});\n\n/*\n * Класс, позволяющий создавать стрелку на карте.\n * Является хелпером к созданию полилинии, у которой задан специальный оверлей.\n * При использовании модулей в реальном проекте рекомендуем размещать их в отдельных файлах.\n */\nymaps.modules.define(\"geoObject.Arrow\", [\n 'Polyline',\n 'overlay.Arrow',\n 'util.extend'\n], function (provide, Polyline, ArrowOverlay, extend) {\n /**\n * @param {Number[][] | Object | ILineStringGeometry} geometry Геометрия ломаной.\n * @param {Object} properties Данные ломаной.\n * @param {Object} options Опции ломаной.\n * Поддерживается тот же набор опций, что и в классе ymaps.Polyline.\n * @param {Number} [options.arrowAngle=20] Угол в градусах между основной линией и линиями стрелки.\n * @param {Number} [options.arrowMinLength=3] Минимальная длина стрелки. Если длина стрелки меньше минимального значения, стрелка не рисуется.\n * @param {Number} [options.arrowMaxLength=20] Максимальная длина стрелки.\n */\n var Arrow = function (geometry, properties, options) {\n return new Polyline(geometry, properties, extend({}, options, {\n lineStringOverlay: ArrowOverlay\n }));\n };\n provide(Arrow);\n});\n\n/*\n * Класс, реализующий интерфейс IOverlay.\n * Получает на вход пиксельную геометрию линии и добавляет стрелку на конце линии.\n */\nymaps.modules.define(\"overlay.Arrow\", [\n 'overlay.Polygon',\n 'util.extend',\n 'event.Manager',\n 'option.Manager',\n 'Event',\n 'geometry.pixel.Polygon'\n], function (provide, PolygonOverlay, extend, EventManager, OptionManager, Event, PolygonGeometry) {\n var domEvents = [\n 'click',\n 'contextmenu',\n 'dblclick',\n 'mousedown',\n 'mouseenter',\n 'mouseleave',\n 'mousemove',\n 'mouseup',\n 'multitouchend',\n 'multitouchmove',\n 'multitouchstart',\n 'wheel'\n ],\n\n /**\n * @param {geometry.pixel.Polyline} pixelGeometry Пиксельная геометрия линии.\n * @param {Object} data Данные оверлея.\n * @param {Object} options Опции оверлея.\n */\n ArrowOverlay = function (pixelGeometry, data, options) {\n // Поля .events и .options обязательные для IOverlay.\n this.events = new EventManager();\n this.options = new OptionManager(options);\n this._map = null;\n this._data = data;\n this._geometry = pixelGeometry;\n this._overlay = null;\n };\n\n ArrowOverlay.prototype = extend(ArrowOverlay.prototype, {\n // Реализовываем все методы и события, которые требует интерфейс IOverlay.\n getData: function () {\n return this._data;\n },\n\n setData: function (data) {\n if (this._data != data) {\n var oldData = this._data;\n this._data = data;\n this.events.fire('datachange', {\n oldData: oldData,\n newData: data\n });\n }\n },\n\n getMap: function () {\n return this._map;\n },\n\n setMap: function (map) {\n if (this._map != map) {\n var oldMap = this._map;\n if (!map) {\n this._onRemoveFromMap();\n }\n this._map = map;\n if (map) {\n this._onAddToMap();\n }\n this.events.fire('mapchange', {\n oldMap: oldMap,\n newMap: map\n });\n }\n },\n\n setGeometry: function (geometry) {\n if (this._geometry != geometry) {\n var oldGeometry = geometry;\n this._geometry = geometry;\n if (this.getMap() && geometry) {\n this._rebuild();\n }\n this.events.fire('geometrychange', {\n oldGeometry: oldGeometry,\n newGeometry: geometry\n });\n }\n },\n\n getGeometry: function () {\n return this._geometry;\n },\n\n getShape: function () {\n return null;\n },\n\n isEmpty: function () {\n return false;\n },\n\n _rebuild: function () {\n this._onRemoveFromMap();\n this._onAddToMap();\n },\n\n _onAddToMap: function () {\n // Военная хитрость - чтобы в прозрачной ломаной хорошо отрисовывались самопересечения,\n // мы рисуем вместо линии многоугольник.\n // Каждый контур многоугольника будет отвечать за часть линии.\n this._overlay = new PolygonOverlay(new PolygonGeometry(this._createArrowContours()));\n this._startOverlayListening();\n // Эта строчка свяжет два менеджера опций.\n // Опции, заданные в родительском менеджере,\n // будут распространяться и на дочерний.\n this._overlay.options.setParent(this.options);\n this._overlay.setMap(this.getMap());\n },\n\n _onRemoveFromMap: function () {\n this._overlay.setMap(null);\n this._overlay.options.setParent(null);\n this._stopOverlayListening();\n },\n\n _startOverlayListening: function () {\n this._overlay.events.add(domEvents, this._onDomEvent, this);\n },\n\n _stopOverlayListening: function () {\n this._overlay.events.remove(domEvents, this._onDomEvent, this);\n },\n\n _onDomEvent: function (e) {\n // Мы слушаем события от дочернего служебного оверлея\n // и прокидываем их на внешнем классе.\n // Это делается для того, чтобы в событии было корректно определено\n // поле target.\n this.events.fire(e.get('type'), new Event({\n target: this\n // Свяжем исходное событие с текущим, чтобы все поля данных дочернего события\n // были доступны в производном событии.\n }, e));\n },\n\n _createArrowContours: function () {\n var contours = [],\n mainLineCoordinates = this.getGeometry().getCoordinates(),\n arrowLength = calculateArrowLength(\n mainLineCoordinates,\n this.options.get('arrowMinLength', 3),\n this.options.get('arrowMaxLength', 20)\n );\n contours.push(getContourFromLineCoordinates(mainLineCoordinates));\n // Будем рисовать стрелку только если длина линии не меньше длины стрелки.\n if (arrowLength > 0) {\n // Создадим еще 2 контура для стрелочек.\n var lastTwoCoordinates = [\n mainLineCoordinates[mainLineCoordinates.length - 2],\n mainLineCoordinates[mainLineCoordinates.length - 1]\n ],\n // Для удобства расчетов повернем стрелку так, чтобы она была направлена вдоль оси y,\n // а потом развернем результаты обратно.\n rotationAngle = getRotationAngle(lastTwoCoordinates[0], lastTwoCoordinates[1]),\n rotatedCoordinates = rotate(lastTwoCoordinates, rotationAngle),\n\n arrowAngle = this.options.get('arrowAngle', 20) / 180 * Math.PI,\n arrowBeginningCoordinates = getArrowsBeginningCoordinates(\n rotatedCoordinates,\n arrowLength,\n arrowAngle\n ),\n firstArrowCoordinates = rotate([\n arrowBeginningCoordinates[0],\n rotatedCoordinates[1]\n ], -rotationAngle),\n secondArrowCoordinates = rotate([\n arrowBeginningCoordinates[1],\n rotatedCoordinates[1]\n ], -rotationAngle);\n\n contours.push(getContourFromLineCoordinates(firstArrowCoordinates));\n contours.push(getContourFromLineCoordinates(secondArrowCoordinates));\n }\n return contours;\n }\n });\n\n function getArrowsBeginningCoordinates (coordinates, arrowLength, arrowAngle) {\n var p1 = coordinates[0],\n p2 = coordinates[1],\n dx = arrowLength * Math.sin(arrowAngle),\n y = p2[1] - arrowLength * Math.cos(arrowAngle);\n return [[p1[0] - dx, y], [p1[0] + dx, y]];\n }\n\n function rotate (coordinates, angle) {\n var rotatedCoordinates = [];\n for (var i = 0, l = coordinates.length, x, y; i < l; i++) {\n x = coordinates[i][0];\n y = coordinates[i][1];\n rotatedCoordinates.push([\n x * Math.cos(angle) - y * Math.sin(angle),\n x * Math.sin(angle) + y * Math.cos(angle)\n ]);\n }\n return rotatedCoordinates;\n }\n\n function getRotationAngle (p1, p2) {\n return Math.PI / 2 - Math.atan2(p2[1] - p1[1], p2[0] - p1[0]);\n }\n\n function getContourFromLineCoordinates (coords) {\n var contour = coords.slice();\n for (var i = coords.length - 2; i > -1; i--) {\n contour.push(coords[i]);\n }\n return contour;\n }\n\n function calculateArrowLength (coords, minLength, maxLength) {\n var linePixelLength = 0;\n for (var i = 1, l = coords.length; i < l; i++) {\n linePixelLength += getVectorLength(\n coords[i][0] - coords[i - 1][0],\n coords[i][1] - coords[i - 1][1]\n );\n if (linePixelLength / 3 > maxLength) {\n return maxLength;\n }\n }\n var finalArrowLength = linePixelLength / 3;\n return finalArrowLength < minLength ? 0 : finalArrowLength;\n }\n\n function getVectorLength (x, y) {\n return Math.sqrt(x * x + y * y);\n }\n\n provide(ArrowOverlay);\n});\n"},"resources":[],"directory":"https://sandbox.api.maps.yandex.net/examples/ru/2.1/arrow/"}
В примере показано, как на основе класса линии Polyline создать модуль для рисования стрелок на карте.
В примере создаются два пользовательских модуля - один отвечает за класс объекта-стрелки, второй модуль включает в себя визуальное отображение стрелки - класс, реализующий интерфейс IOverlay. В примере модули располагаются в одном файле. При разработке проекта рекомендуется помещать модули в отдельные файлы.