(function(L) { 'use strict'; var classToExtend = 'Class'; if (L.version.charAt(0) !== '0') { classToExtend = 'Layer'; } L.EdgeMarker = L[classToExtend].extend({ options: { distanceOpacity: false, distanceOpacityFactor: 4, layerGroup: null, rotateIcons: true, findEdge : function (map){ return L.bounds([0,0], map.getSize()); }, icon: L.icon({ iconUrl: L.Icon.Default.imagePath + '/edge-arrow-marker.png', clickable: true, iconSize: [48, 48], iconAnchor: [24, 24] }) }, initialize: function(options) { L.setOptions(this, options); }, addTo: function(map) { this._map = map; // add a method to get applicable features if (typeof map._getFeatures !== 'function') { L.extend(map, { _getFeatures: function() { var out = []; for (var l in this._layers) { if (typeof this._layers[l].getLatLng !== 'undefined') { out.push(this._layers[l]); } } return out; } }); } map.on('move', this._addEdgeMarkers, this); map.on('viewreset', this._addEdgeMarkers, this); this._addEdgeMarkers(); map.addLayer(this); return this; }, destroy: function() { if (this._map && this._borderMarkerLayer) { this._map.off('move', this._addEdgeMarkers, this); this._map.off('viewreset', this._addEdgeMarkers, this); this._borderMarkerLayer.clearLayers(); this._map.removeLayer(this._borderMarkerLayer); delete this._map._getFeatures; this._borderMarkerLayer = undefined; } }, onClick: function(e) { this._map.setView(e.target.options.latlng, this._map.getZoom()); }, onAdd: function() {}, _borderMarkerLayer: undefined, _addEdgeMarkers: function() { if (typeof this._borderMarkerLayer === 'undefined') { this._borderMarkerLayer = new L.LayerGroup(); } this._borderMarkerLayer.clearLayers(); var features = []; if (this.options.layerGroup != null) { features = this.options.layerGroup.getLayers(); } else { features = this._map._getFeatures(); } var mapPixelBounds = this.options.findEdge(this._map); var markerWidth = this.options.icon.options.iconSize[0]; var markerHeight = this.options.icon.options.iconSize[1]; for (var i = 0; i < features.length; i++) { var currentMarkerPosition = this._map.latLngToContainerPoint( features[i].getLatLng() ); if (currentMarkerPosition.y < mapPixelBounds.min.y || currentMarkerPosition.y > mapPixelBounds.max.y || currentMarkerPosition.x > mapPixelBounds.max.x || currentMarkerPosition.x < mapPixelBounds.min.x ) { // get pos of marker var x = currentMarkerPosition.x; var y = currentMarkerPosition.y; var markerDistance; // we want to place EdgeMarker on the line from center screen to target, // and against the border of the screen // we know angel and its x or y cordiante // (depending if we want to place it against top/bottom edge or left right edge) // fromthat we can calculate the other cordinate var center = mapPixelBounds.getCenter(); var rad = Math.atan2(center.y - y, center.x - x); var rad2TopLeftcorner = Math.atan2(center.y-mapPixelBounds.min.y,center.x-mapPixelBounds.min.x); // target is in between diagonals window/ hourglass // more out in y then in x if (Math.abs(rad) > rad2TopLeftcorner && Math.abs (rad) < Math.PI -rad2TopLeftcorner) { // bottom out if (y < center.y ){ y = mapPixelBounds.min.y + markerHeight/2; x = center.x - (center.y-y) / Math.tan(Math.abs(rad)); markerDistance = currentMarkerPosition.y - mapPixelBounds.y; // top out }else{ y = mapPixelBounds.max.y - markerHeight/2; x = center.x - (y-center.y)/ Math.tan(Math.abs(rad)); markerDistance = -currentMarkerPosition.y; } }else { // left out if (x < center.x ){ x = mapPixelBounds.min.x + markerWidth/2; y = center.y - (center.x-x ) *Math.tan(rad); markerDistance = -currentMarkerPosition.x; // right out }else{ x = mapPixelBounds.max.x - markerWidth/2; y = center.y + (x - center.x) *Math.tan(rad); markerDistance = currentMarkerPosition.x - mapPixelBounds.x; } } // correction so that is always has same distance to edge // top out (top has y=0) if (y < mapPixelBounds.min.y + markerHeight/2) { y = mapPixelBounds.min.y + markerHeight/2; // bottom out } else if (y > mapPixelBounds.max.y - markerHeight/2) { y = mapPixelBounds.max.y - markerHeight/2 ; } // right out if (x > mapPixelBounds.max.x - markerWidth / 2) { x = mapPixelBounds.max.x - markerWidth / 2; // left out } else if (x < markerWidth / 2) { x = mapPixelBounds.min.x + markerWidth / 2; } // change opacity on distance var newOptions = this.options; if (this.options.distanceOpacity) { newOptions.fillOpacity = (100 - markerDistance / this.options.distanceOpacityFactor) / 100; } // rotate markers if (this.options.rotateIcons) { var angle = rad / Math.PI * 180; newOptions.angle = angle; } var ref = { latlng: features[i].getLatLng() }; newOptions = L.extend({}, newOptions, ref); var marker = L.rotatedMarker( this._map.containerPointToLatLng([x, y]), newOptions ).addTo(this._borderMarkerLayer); marker.on('click', this.onClick, marker); } } if (!this._map.hasLayer(this._borderMarkerLayer)) { this._borderMarkerLayer.addTo(this._map); } } }); /* * L.rotatedMarker class is taken from https://github.com/bbecquet/Leaflet.PolylineDecorator. */ L.RotatedMarker = L.Marker.extend({ options: { angle: 0 }, statics: { TRANSFORM_ORIGIN: L.DomUtil.testProp([ 'transformOrigin', 'WebkitTransformOrigin', 'OTransformOrigin', 'MozTransformOrigin', 'msTransformOrigin' ]) }, _initIcon: function() { L.Marker.prototype._initIcon.call(this); this._icon.style[L.RotatedMarker.TRANSFORM_ORIGIN] = '50% 50%'; }, _setPos: function(pos) { L.Marker.prototype._setPos.call(this, pos); if (L.DomUtil.TRANSFORM) { // use the CSS transform rule if available this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)'; } else if (L.Browser.ie) { // fallback for IE6, IE7, IE8 var rad = this.options.angle * (Math.PI / 180), costheta = Math.cos(rad), sintheta = Math.sin(rad); this._icon.style.filter += " progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=" + costheta + ', M12=' + -sintheta + ', M21=' + sintheta + ', M22=' + costheta + ')'; } }, setAngle: function(ang) { this.options.angle = ang; } }); L.rotatedMarker = function(pos, options) { return new L.RotatedMarker(pos, options); }; L.edgeMarker = function(options) { return new L.EdgeMarker(options); }; })(L);