Skip to content

Instantly share code, notes, and snippets.

@ronihcohen
Created April 5, 2015 15:30
Show Gist options
  • Save ronihcohen/add2a0a05ad113c43c35 to your computer and use it in GitHub Desktop.
Save ronihcohen/add2a0a05ad113c43c35 to your computer and use it in GitHub Desktop.
Angular google maps distance widget directive.
'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