Source: modern-store-locator-circle.js

/**

 * @namespace WPGMZA

 * @module ModernStoreLocatorCircle

 * @requires WPGMZA

 */

(function($) {

	

	/**

	 * This module is the modern store locator circle

	 * @constructor

	 */

	WPGMZA.ModernStoreLocatorCircle = function(map_id, settings) {

		var self = this;

		var map;

		

		if(WPGMZA.isProVersion())

			map = this.map = MYMAP[map_id].map;

		else

			map = this.map = MYMAP.map;

		

		this.map_id = map_id;

		this.mapElement = map.element;

		this.mapSize = {

			width:  $(this.mapElement).width(),

			height: $(this.mapElement).height()

		};

			

		this.initCanvasLayer();

		

		this.settings = {

			center: new WPGMZA.LatLng(0, 0),

			radius: 1,

			color: "#63AFF2",

			

			shadowColor: "white",

			shadowBlur: 4,

			

			centerRingRadius: 10,

			centerRingLineWidth: 3,



			numInnerRings: 9,

			innerRingLineWidth: 1,

			innerRingFade: true,

			

			numOuterRings: 7,

			

			ringLineWidth: 1,

			

			mainRingLineWidth: 2,

			

			numSpokes: 6,

			spokesStartAngle: Math.PI / 2,

			

			numRadiusLabels: 6,

			radiusLabelsStartAngle: Math.PI / 2,

			radiusLabelFont: "13px sans-serif",

			

			visible: false

		};

		

		if(settings)

			this.setOptions(settings);

	};

	

	WPGMZA.ModernStoreLocatorCircle.createInstance = function(map, settings) {

		

		if(WPGMZA.settings.engine == "google-maps")

			return new WPGMZA.GoogleModernStoreLocatorCircle(map, settings);

		else

			return new WPGMZA.OLModernStoreLocatorCircle(map, settings);

		

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.initCanvasLayer = function() {

		

	}

	

	WPGMZA.ModernStoreLocatorCircle.prototype.onResize = function(event) { 

		this.draw();

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.onUpdate = function(event) { 

		this.draw();

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.setOptions = function(options) {

		for(var name in options)

		{

			var functionName = "set" + name.substr(0, 1).toUpperCase() + name.substr(1);

			

			if(typeof this[functionName] == "function")

				this[functionName](options[name]);

			else

				this.settings[name] = options[name];

		}

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.getResolutionScale = function() {

		return window.devicePixelRatio || 1;

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.getCenter = function() {

		return this.getPosition();

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.setCenter = function(value) {

		this.setPosition(value);

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.getPosition = function() {

		return this.settings.center;

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.setPosition = function(position) {

		this.settings.center = position;

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.getRadius = function() {

		return this.settings.radius;

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.setRadius = function(radius) {

		

		if(isNaN(radius))

			throw new Error("Invalid radius");

		

		this.settings.radius = radius;

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.getVisible = function(visible) {

		return this.settings.visible;

	};

	

	WPGMZA.ModernStoreLocatorCircle.prototype.setVisible = function(visible) {

		this.settings.visible = visible;

	};

	

	/**

	 * This function transforms a km radius into canvas space

	 * @return number

	 */

	WPGMZA.ModernStoreLocatorCircle.prototype.getTransformedRadius = function(km)

	{

		throw new Error("Abstract function called");

	}

	

	WPGMZA.ModernStoreLocatorCircle.prototype.getContext = function(type)

	{

		throw new Error("Abstract function called");

	}

	

	WPGMZA.ModernStoreLocatorCircle.prototype.getCanvasDimensions = function()

	{

		throw new Error("Abstract function called");

	}

	

	WPGMZA.ModernStoreLocatorCircle.prototype.draw = function() {

		

		var settings = this.settings;

		var canvasDimensions = this.getCanvasDimensions();

		

        var canvasWidth = canvasDimensions.width;

        var canvasHeight = canvasDimensions.height;

		

		var map = this.map;

		var resolutionScale = this.getResolutionScale();

		

		context = this.getContext("2d");

        context.clearRect(0, 0, canvasWidth, canvasHeight);



		if(!settings.visible)

			return;

		

		context.shadowColor = settings.shadowColor;

		context.shadowBlur = settings.shadowBlur;

		

		// NB: 2018/02/13 - Left this here in case it needs to be calibrated more accurately

		/*if(!this.testCircle)

		{

			this.testCircle = new google.maps.Circle({

				strokeColor: "#ff0000",

				strokeOpacity: 0.5,

				strokeWeight: 3,

				map: this.map,

				center: this.settings.center

			});

		}

		

		this.testCircle.setCenter(settings.center);

		this.testCircle.setRadius(settings.radius * 1000);*/

		

        // Reset transform

        context.setTransform(1, 0, 0, 1, 0, 0);

        

        var scale = Math.pow(2, map.getZoom()) * resolutionScale;

        context.scale(scale, scale);



		// Translate by world origin

		var offset = this.getWorldOriginOffset();

		context.translate(offset.x, offset.y);



        // Get center and project to pixel space

		var center = new WPGMZA.LatLng(this.settings.center);

		var worldPoint = this.getCenterPixels();

		

		var rgba = WPGMZA.hexToRgba(settings.color);

		var ringSpacing = this.getTransformedRadius(settings.radius) / (settings.numInnerRings + 1);

		

		// TODO: Implement gradients for color and opacity

		

		// Inside circle (fixed?)

        context.strokeStyle = settings.color;

		context.lineWidth = (1 / scale) * settings.centerRingLineWidth;

		

		context.beginPath();

		context.arc(

			worldPoint.x, 

			worldPoint.y, 

			this.getTransformedRadius(settings.centerRingRadius) / scale, 0, 2 * Math.PI

		);

		context.stroke();

		context.closePath();

		

		// Spokes

		var radius = this.getTransformedRadius(settings.radius) + (ringSpacing * settings.numOuterRings) + 1;

		var grad = context.createRadialGradient(0, 0, 0, 0, 0, radius);

		var rgba = WPGMZA.hexToRgba(settings.color);

		var start = WPGMZA.rgbaToString(rgba), end;

		var spokeAngle;

		

		rgba.a = 0;

		end = WPGMZA.rgbaToString(rgba);

		

		grad.addColorStop(0, start);

		grad.addColorStop(1, end);

		

		context.save();

		

		context.translate(worldPoint.x, worldPoint.y);

		context.strokeStyle = grad;

		context.lineWidth = 2 / scale;

		

		for(var i = 0; i < settings.numSpokes; i++)

		{

			spokeAngle = settings.spokesStartAngle + (Math.PI * 2) * (i / settings.numSpokes);

			

			x = Math.cos(spokeAngle) * radius;

			y = Math.sin(spokeAngle) * radius;

			

			context.setLineDash([2 / scale, 15 / scale]);

			

			context.beginPath();

			context.moveTo(0, 0);

			context.lineTo(x, y);

			context.stroke();

		}

		

		context.setLineDash([]);

		

		context.restore();

		

		// Inner ringlets

		context.lineWidth = (1 / scale) * settings.innerRingLineWidth;

		

		for(var i = 1; i <= settings.numInnerRings; i++)

		{

			var radius = i * ringSpacing;

			

			if(settings.innerRingFade)

				rgba.a = 1 - (i - 1) / settings.numInnerRings;

			

			context.strokeStyle = WPGMZA.rgbaToString(rgba);

			

			context.beginPath();

			context.arc(worldPoint.x, worldPoint.y, radius, 0, 2 * Math.PI);

			context.stroke();

			context.closePath();

		}

		

		// Main circle

		context.strokeStyle = settings.color;

		context.lineWidth = (1 / scale) * settings.centerRingLineWidth;

		

		context.beginPath();

		context.arc(worldPoint.x, worldPoint.y, this.getTransformedRadius(settings.radius), 0, 2 * Math.PI);

		context.stroke();

		context.closePath();

		

		// Outer ringlets

		var radius = radius + ringSpacing;

		for(var i = 0; i < settings.numOuterRings; i++)

		{

			if(settings.innerRingFade)

				rgba.a = 1 - i / settings.numOuterRings;

			

			context.strokeStyle = WPGMZA.rgbaToString(rgba);

			

			context.beginPath();

			context.arc(worldPoint.x, worldPoint.y, radius, 0, 2 * Math.PI);

			context.stroke();

			context.closePath();

		

			radius += ringSpacing;

		}

		

		// Text

		if(settings.numRadiusLabels > 0)

		{

			var m;

			var radius = this.getTransformedRadius(settings.radius);

			var clipRadius = (12 * 1.1) / scale;

			var x, y;

			

			if(m = settings.radiusLabelFont.match(/(\d+)px/))

				clipRadius = (parseInt(m[1]) / 2 * 1.1) / scale;

			

			context.font = settings.radiusLabelFont;

			context.textAlign = "center";

			context.textBaseline = "middle";

			context.fillStyle = settings.color;

			

			context.save();

			

			context.translate(worldPoint.x, worldPoint.y)

			

			for(var i = 0; i < settings.numRadiusLabels; i++)

			{

				var spokeAngle = settings.radiusLabelsStartAngle + (Math.PI * 2) * (i / settings.numRadiusLabels);

				var textAngle = spokeAngle + Math.PI / 2;

				var text = settings.radiusString;

				var width;

				

				if(Math.sin(spokeAngle) > 0)

					textAngle -= Math.PI;

				

				x = Math.cos(spokeAngle) * radius;

				y = Math.sin(spokeAngle) * radius;

				

				context.save();

				

				context.translate(x, y);

				

				context.rotate(textAngle);

				context.scale(1 / scale, 1 / scale);

				

				width = context.measureText(text).width;

				height = width / 2;

				context.clearRect(-width, -height, 2 * width, 2 * height);

				

				context.fillText(settings.radiusString, 0, 0);

				

				context.restore();

			}

			

			context.restore();

		}

	}

	

})(jQuery);