Created
April 5, 2015 15:30
-
-
Save ronihcohen/add2a0a05ad113c43c35 to your computer and use it in GitHub Desktop.
Angular google maps distance widget directive.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** | |
* @ngdoc directive | |
* @name directive:distanceWidget | |
* @description | |
* # distanceWidget for angular-google-maps | |
*/ | |
angular.module('app') | |
.directive('distanceWidget', function (uiGmapIsReady, $filter) { | |
return { | |
restrict: 'E', | |
scope: { | |
enable: '=', | |
data: '=' | |
}, | |
link: function postLink(scope, element, attrs) { | |
uiGmapIsReady.promise(1).then(function(instances) { | |
instances.forEach(function(inst) { | |
var map = inst.map; | |
scope.$watch('enable',function(enable){ | |
if (enable){ | |
scope.init(map); | |
map.setOptions({draggable: false}); | |
} else { | |
google.maps.event.clearInstanceListeners(map); | |
map.setOptions({draggable: true}); | |
} | |
}) | |
}); | |
}); | |
scope.init = function(map){ | |
/** | |
* A distance widget that will display a circle that can be resized and will | |
* provide the radius in km. | |
* | |
* @param {google.maps.Map} map The map on which to attach the distance widget. | |
* | |
* @constructor | |
*/ | |
function DistanceWidget(map,mouseClickPosition) { | |
this.set('map', map); | |
this.set('position',mouseClickPosition); | |
scope.marker = new google.maps.Marker({ | |
draggable: false, | |
icon: { | |
url: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', | |
size: new google.maps.Size(30,30), | |
} | |
}); | |
google.maps.event.addListener(scope.marker, 'mouseup', scope.clear); | |
google.maps.event.addListener(scope.marker, 'mousemove', scope.mouseMoveFnc); | |
// Bind the marker map property to the DistanceWidget map property | |
scope.marker.bindTo('map', this); | |
// Bind the marker position property to the DistanceWidget position | |
// property | |
scope.marker.bindTo('position', this); | |
// Create a new radius widget | |
scope.radiusWidget = new RadiusWidget(); | |
// Bind the radiusWidget map to the DistanceWidget map | |
scope.radiusWidget.bindTo('map', this); | |
// Bind the radiusWidget center to the DistanceWidget position | |
scope.radiusWidget.bindTo('center', this, 'position'); | |
// Bind to the radiusWidgets' distance property | |
this.bindTo('distance', scope.radiusWidget); | |
// Bind to the radiusWidgets' bounds property | |
this.bindTo('bounds', scope.radiusWidget); | |
infoWindowWidget(); | |
scope.infobox.bindTo('map', this); | |
scope.infobox.bindTo('content', this, 'radius'); | |
} | |
DistanceWidget.prototype = new google.maps.MVCObject(); | |
/** | |
* A radius widget that add a circle to a map and centers on a marker. | |
* | |
* @constructor | |
*/ | |
function RadiusWidget() { | |
scope.circle = new google.maps.Circle({ | |
strokeWeight: 2, | |
strokeColor: '#00A2FF', | |
fillColor: '#00A2FF', | |
fillOpacity: 0.2 | |
}); | |
google.maps.event.addListener(scope.circle, 'mouseup', scope.clear); | |
google.maps.event.addListener(scope.circle, 'mousemove', scope.mouseMoveFnc); | |
this.set('distance', 0); | |
// Bind the RadiusWidget bounds property to the circle bounds property. | |
this.bindTo('bounds', scope.circle); | |
// Bind the circle center to the RadiusWidget center property | |
scope.circle.bindTo('center', this); | |
// Bind the circle map to the RadiusWidget map | |
scope.circle.bindTo('map', this); | |
// Bind the circle radius property to the RadiusWidget radius property | |
scope.circle.bindTo('radius', this); | |
this.addSizer_(); | |
} | |
RadiusWidget.prototype = new google.maps.MVCObject(); | |
/** | |
* Update the radius when the distance has changed. | |
*/ | |
RadiusWidget.prototype.distance_changed = function() { | |
this.set('radius', this.get('distance') * 1000); | |
}; | |
/** | |
* Add the sizer marker to the map. | |
* | |
* @private | |
*/ | |
RadiusWidget.prototype.addSizer_ = function() { | |
scope.sizer = new google.maps.Marker({ | |
draggable: false, | |
icon: { | |
url: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', | |
size: new google.maps.Size(30,30) | |
} | |
}); | |
google.maps.event.addListener(scope.sizer, 'mouseup', scope.clear); | |
google.maps.event.addListener(scope.sizer, 'mousemove', scope.mouseMoveFnc); | |
scope.sizer.bindTo('map', this); | |
scope.sizer.bindTo('position', this, 'sizer_position'); | |
scope.lineBetweenMarkers = new google.maps.Polyline({ | |
path: [ scope.marker.getPosition(), scope.marker.getPosition()], | |
strokeColor: "#222222", | |
strokeOpacity: 1.0, | |
strokeWeight: 4 | |
}); | |
scope.lineBetweenMarkers.bindTo('map', this); | |
google.maps.event.addListener(scope.lineBetweenMarkers, 'mouseup', scope.clear); | |
google.maps.event.addListener(scope.lineBetweenMarkers, 'mousemove', scope.mouseMoveFnc); | |
}; | |
/** | |
* Update the center of the circle and position the sizer back on the line. | |
* | |
* Position is bound to the DistanceWidget so this is expected to change when | |
* the position of the distance widget is changed. | |
*/ | |
RadiusWidget.prototype.center_changed = function() { | |
var bounds = this.get('bounds'); | |
// Bounds might not always be set so check that it exists first. | |
if (bounds) { | |
var lng = bounds.getNorthEast().lng(); | |
// Put the sizer at center, right on the circle. | |
var position = new google.maps.LatLng(this.get('center').lat(), lng); | |
this.set('sizer_position', position); | |
} | |
}; | |
/** | |
* Calculates the distance between two latlng locations in km. | |
* @see http://www.movable-type.co.uk/scripts/latlong.html | |
* | |
* @param {google.maps.LatLng} p1 The first lat lng point. | |
* @param {google.maps.LatLng} p2 The second lat lng point. | |
* @return {number} The distance between the two points in km. | |
* @private | |
*/ | |
RadiusWidget.prototype.distanceBetweenPoints_ = function(p1, p2) { | |
if (!p1 || !p2) { | |
return 0; | |
} | |
var R = 6371; // Radius of the Earth in km | |
var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; | |
var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; | |
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + | |
Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * | |
Math.sin(dLon / 2) * Math.sin(dLon / 2); | |
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |
var d = R * c; | |
return d; | |
}; | |
/** | |
* Set the distance of the circle based on the position of the sizer. | |
*/ | |
RadiusWidget.prototype.setDistance = function() { | |
// As the sizer is being dragged, its position changes. Because the | |
// RadiusWidget's sizer_position is bound to the sizer's position, it will | |
// change as well. | |
var pos = this.get('sizer_position'); | |
var center = this.get('center'); | |
var distance = this.distanceBetweenPoints_(center, pos); | |
// Set the distance property for any objects that are bound to it | |
this.set('distance', distance); | |
}; | |
RadiusWidget.prototype.getDistance = function() { | |
// As the sizer is being dragged, its position changes. Because the | |
// RadiusWidget's sizer_position is bound to the sizer's position, it will | |
// change as well. | |
var pos = this.get('sizer_position'); | |
var center = this.get('center'); | |
var distance = this.distanceBetweenPoints_(center, pos); | |
distance = $filter('number')(distance, 1); | |
// Get the distance property for any objects that are bound to it | |
return distance; | |
}; | |
/** | |
* Events Functions | |
*/ | |
scope.mouseMoveFnc = function(e){ | |
if (scope.isDrawing) { | |
scope.sizer.setPosition(e.latLng) | |
scope.radiusWidget.setDistance(e.latLng) | |
// Infobox | |
var contentDivAngle = getAngleBetweenTwoPoints(scope.marker.getPosition(), e.latLng); | |
scope.infobox.setContent(getInfoWindowContent(contentDivAngle)); | |
var midPoint = getMidPoint(scope.marker.getPosition(), e.latLng); | |
scope.infobox.setPosition(new google.maps.LatLng(midPoint.lat(), midPoint.lng()) ); | |
scope.lineBetweenMarkers.setPath([scope.marker.getPosition(), e.latLng]); | |
} | |
} | |
scope.clear = function(){ | |
if (scope.isDrawing){ | |
scope.data = { | |
position: scope.distanceWidget.get('position'), | |
distance: scope.distanceWidget.get('distance'), | |
radius: scope.radiusWidget.get('radius') | |
} | |
scope.$apply(); | |
scope.circle.setMap(null); | |
scope.marker.setMap(null); | |
scope.sizer.setMap(null); | |
scope.infobox.setMap(null); | |
delete scope.radiusWidget; | |
delete scope.distanceWidget; | |
delete scope.sizer; | |
delete scope.circle; | |
delete scope.marker; | |
delete scope.infobox | |
scope.isDrawing = false; | |
} | |
} | |
scope.isDrawing = false; | |
/** | |
* Events Listeners | |
*/ | |
google.maps.event.addListener(map, 'mousedown', function(e) { | |
if (!scope.isDrawing){ | |
scope.distanceWidget = new DistanceWidget(map,e.latLng); | |
scope.isDrawing = true; | |
google.maps.event.addListener(map, 'mousemove', scope.mouseMoveFnc); | |
} | |
google.maps.event.addListener(map, 'mouseup', scope.clear); | |
}); | |
/** | |
* Infobox | |
* Will show a label near the radius line with the distanse in km | |
*/ | |
function infoWindowWidget() { | |
var myOptions = { | |
content: getInfoWindowContent() | |
,disableAutoPan: false | |
,pixelOffset: new google.maps.Size(-20, -20) | |
,maxWidth: 0 | |
,zIndex: null | |
,position: scope.marker.getPosition() | |
,closeBoxURL: "" | |
,isHidden: false | |
,enableEventPropagation: true | |
}; | |
scope.infobox = new InfoBox(myOptions); | |
scope.infobox.open(map, scope.marker); | |
google.maps.event.addListener(scope.infobox, 'mouseup',scope.mouseUpFnc); | |
google.maps.event.addListener(scope.infobox, 'mousemove', scope.mouseMoveFnc); | |
} | |
function getInfoWindowContent(transformAngle) { | |
var boxText = document.createElement("div"); | |
calcElemPixelOffset(boxText, transformAngle); | |
jQuery(boxText).addClass('radius-label'); | |
//boxText.innerHTML = scope.radiusWidget.getDistance() + ' ' + $t.getTranslation().km; | |
boxText.innerHTML = scope.radiusWidget.getDistance() + ' km'; | |
return boxText | |
} | |
function calcElemPixelOffset(contentDiv, transformAngle) { | |
var offset = -20; | |
var offsetY = -20; | |
if (angular.isDefined(transformAngle)) { | |
if ( 0 <= transformAngle && transformAngle <= 90) { | |
/** Set the offset of the text. */ | |
if (45 <= transformAngle && transformAngle < 70) { | |
offset = -28; | |
} else if (20 <= transformAngle && transformAngle < 45) { | |
offset = -35; | |
} else if (0 <= transformAngle && transformAngle < 20) { | |
offset = -40; | |
} | |
scope.infobox.setOptions({pixelOffset: new google.maps.Size(offset, -20)}); | |
transformAngle -= 90; | |
} else if ( -90 <= transformAngle && transformAngle < 0) { | |
offset = -20; | |
offsetY = -17; | |
scope.infobox.setOptions({pixelOffset: new google.maps.Size(offset, offsetY)}); | |
transformAngle += 90; | |
} else if ( -90 > transformAngle && transformAngle >= -180) { | |
/** Set the offset of the text. */ | |
if (-110 <= transformAngle && transformAngle < -90) { | |
offset = -27; | |
} else if (-140 <= transformAngle && transformAngle < -110) { | |
offset = -35; | |
} else if (-160 <= transformAngle && transformAngle < -140) { | |
offset = -42; | |
} else { | |
offset = -47; | |
} | |
offsetY = -20; | |
scope.infobox.setOptions({pixelOffset: new google.maps.Size(offset, offsetY)}); | |
transformAngle += 90; | |
} else if ( 90 < transformAngle && transformAngle <= 180) { | |
transformAngle -= 90; | |
scope.infobox.setOptions({pixelOffset: new google.maps.Size(-20, -20)}); | |
} | |
jQuery(contentDiv).css('transform', 'rotate(' + transformAngle +'deg)'); | |
} | |
} | |
function getMidPoint(pointA, pointB) { | |
var bound = new google.maps.LatLngBounds(); | |
bound.extend( new google.maps.LatLng(pointA.lat(), pointA.lng()) ); | |
bound.extend( new google.maps.LatLng(pointB.lat(), pointB.lng()) ); | |
return bound.getCenter(); | |
} | |
function getAngleBetweenTwoPoints(pointA, pointB) { | |
return google.maps.geometry.spherical.computeHeading(pointA,pointB); | |
} | |
} | |
} | |
}; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment