Flash- circle not rotating inside another circle - actionscript-3

I am trying to make the small circles to be able to rotate inside the large circle. But what i got is that the small circles are rotating outside the large circle.
Here is my code:
import flash.events.Event;
import flash.events.MouseEvent;
var Rec:Number=-1;
stage.addEventListener(Event.ENTER_FRAME,EntFrame);
function EntFrame(e:Event):void
{
if (Rec == -1)
{
CircleL.rotation-= 2;
}
}
var cenX = CircleL.x;
var cenY = CircleL.y;
var ccStep = .01;
var twoPI = 2 * Math.PI;
var circleSNum1:Number = Math.random();
var circleSNum2:Number = Math.random();
var circleSNum3:Number = Math.random();
var circleSNum4:Number = Math.random();
var circleSNum5:Number = Math.random();
function randomRange(minNum:Number, maxNum:Number):Number
{
return (Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
}
var radius1 = randomRange(10, 90);
var radius2 = randomRange(10, 90);
var radius3 = randomRange(10, 90);
var radius4 = randomRange(10, 90);
var radius = randomRange(10, 90);
function move(event:Event):void
{
CircleL.CircleS1.x = cenX + Math.cos(circleSNum1 * twoPI) * radius;
CircleL.CircleS1.y = cenY + Math.sin(circleSNum1 * twoPI) * radius;
CircleL.CircleS2.x = cenX + Math.cos(circleSNum2 * twoPI) * radius2;
CircleL.CircleS2.y = cenY + Math.sin(circleSNum2 * twoPI) * radius2;
CircleL.CircleS3.x = cenX + Math.cos(circleSNum3 * twoPI) * radius3;
CircleL.CircleS3.y = cenY + Math.sin(circleSNum3 * twoPI) * radius3;
CircleL.CircleS4.x = cenX + Math.cos(circleSNum4 * twoPI) * radius4;
CircleL.CircleS4.y = cenY + Math.sin(circleSNum4 * twoPI) * radius4;
CircleL.CircleS5.x = cenX + Math.cos(circleSNum5 * twoPI) * radius1;
CircleL.CircleS5.y = cenY + Math.sin(circleSNum5 * twoPI) * radius1;
}
addEventListener(Event.ENTER_FRAME,move);
Here is how it looks like when I run it:

The problem is likely the way your circles are nested and how you do the calculation.
The .x and .y of a DisplayObject properties are always with respect to the origin of the parent. Looking at your code:
CircleL.CircleS1
CircleL.CircleS2
All the smaller circles have the bigger one as a container. Your calculation however:
cenX + Math.cos(circleSNum1 * twoPI) * radius;
// aka
CircleL.x + Math.cos(circleSNum1 * twoPI) * radius;
starts with adding the coordinates of the parent. This is in contradiction to the above, because "with respect to their parent" already includes the term CircleL.x. You have two options:
Keep the code, but move all the smaller circles outside their parent, so that they are "siblings" of the big circle.
Keep the structure, but change the code to accommodate for that structure and remove the position of the parent from the calculation.

Related

Flash-position of small circle keep changing when i click "Resume"

I am trying to make small circles moving inside a big circle and also having buttons to pause,resume and reverse it. But when i pause and resume it, it will change the small circle's position but i want it to stay at the original position. Is there anyway to do it? and also is there a way to make reverse the direction when i click?(eg. starting is cw when i click "reverse" it will turn anti-cw and when i click "reverse" again it will go cw)
This is my code for action layer:
var Rec:Number=1;
stage.addEventListener(Event.ENTER_FRAME,EntFrame);
function EntFrame(e:Event):void
{
if (Rec >= 1)
{
CircleL.rotation-= 2;
}
else if(Rec <= -1)
{
CircleL.rotation+=2;
}
}
var twoPI = 2 * Math.PI;
var circleSNum1:Number = Math.random();
var circleSNum2:Number = Math.random();
var circleSNum3:Number = Math.random();
var circleSNum4:Number = Math.random();
var circleSNum5:Number = Math.random();
function randomRange(minNum:Number, maxNum:Number):Number
{
return (Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
}
var radius = randomRange(10, 90);
function move(event:Event):void{
CircleL.CircleS1.x = Math.cos(circleSNum1 * twoPI) * radius;
CircleL.CircleS1.y = Math.sin(circleSNum1 * twoPI) * radius;
CircleL.CircleS2.x = Math.cos(circleSNum2 * twoPI) * radius;
CircleL.CircleS2.y = Math.sin(circleSNum2 * twoPI) * radius;
CircleL.CircleS3.x = Math.cos(circleSNum3 * twoPI) * radius;
CircleL.CircleS3.y = Math.sin(circleSNum3 * twoPI) * radius;
CircleL.CircleS4.x = Math.cos(circleSNum4 * twoPI) * radius;
CircleL.CircleS4.y = Math.sin(circleSNum4 * twoPI) * radius;
CircleL.CircleS5.x = Math.cos(circleSNum5 * twoPI) * radius;
CircleL.CircleS5.y = Math.sin(circleSNum5 * twoPI) * radius;
}
addEventListener(Event.ENTER_FRAME,move);
Pause.addEventListener(MouseEvent.CLICK, clickPause);
function clickPause(Event:MouseEvent):void{
gotoAndStop("Pause");
Rec=0
}
Resume.addEventListener(MouseEvent.CLICK, clickResume);
function clickResume(Event:MouseEvent):void{
gotoAndStop("Resume");
Rec=1
}
Reverse.addEventListener(MouseEvent.CLICK, clickReverse);
function clickReverse(Event:MouseEvent):void{
gotoAndStop("Reverse");
Rec=-1
}
When i test it is this position
After i click "pause" and "resume" the position of small circle changed
Try removing the Enter Frame event listener when you want to pause and add it back when you need to resume. The enter frame event still triggers even if you call stop(); (gotoAndStop in this case). So the circle still moves even if you can't see it because you've jumped to another frame. When you come back to it (resume) it will look like it moved but it has never stopped.
Pause.addEventListener(MouseEvent.CLICK, clickPause);
function clickPause(Event:MouseEvent):void{
//remove event listener
stage.removeEventListener(Event.ENTER_FRAME,EntFrame);
gotoAndStop("Pause");
Rec=0
}
Resume.addEventListener(MouseEvent.CLICK, clickResume);
function clickResume(Event:MouseEvent):void{
//add event listener
stage.addEventListener(Event.ENTER_FRAME,EntFrame);
gotoAndStop("Resume");
Rec=1
}
Also, try writing your ActionScript in external files and remove it from the time line, also use classes...it's not that hard and will make you a better developer in the end.

Rotate image around center point of it's container

I have Image component inside some container with clipAndEnableScrolling property set to true. I need a static method which gets this Image, rotation angle and rotates Image around center point of container without loosing any previous transformations. The best method I've created adds error after few rotations.
I thing it must work like this
public static function rotateImageAroundCenterOfViewPort(image:Image, value:int):void
{
// Calculate rotation and shifts
var bounds:Rectangle = image.getBounds(image.parent);
var angle:Number = value - image.rotation;
var radians:Number = angle * (Math.PI / 180.0);
var shiftByX:Number = image.parent.width / 2 - bounds.x;
var shiftByY:Number = image.parent.height / 2 - bounds.y;
// Perform rotation
var matrix:Matrix = new Matrix();
matrix.translate(-shiftByX, -shiftByY);
matrix.rotate(radians);
matrix.translate(+shiftByX, +shiftByY);
matrix.concat(image.transform.matrix);
image.transform.matrix = matrix;
}
but it doesn't. Looks like I can't understand how transformation works(
If you are trying to rotate the object around it's center, I think you'll want some more like this:
var matrix:Matrix = image.transform.matrix;
var rect:Rectangle = image.getBounds( insertParentObject );
//translate matrix to center
matrix.translate(- ( rect.left + ( rect.width/2 ) ), - ( rect.top + ( rect.height/2 ) ) );
matrix.rotate(radians);
//translate back
matrix.translate(rect.left + ( rect.width/2 ), rect.top + ( rect.height/2 ) );
image.transform.matrix = matrix;
Also here is a link to the same SO question with varying answers including the one I provided:
Flex/ActionScript - rotate Sprite around its center
As discussed in the comments if you are looking to rotate an object around a point (that is the center of your container), here's a function that I think would work:
//pass rotateAmount as the angle you want to rotate in degrees
private function rotateAround( rotateAmount:Number, obj:DisplayObject, origin:Point, distance:Number = 100 ):void {
var radians:Number = rotateAmount * Math.PI / 180;
obj.x = origin.x + distance * Math.cos( radians );
obj.y = origin.y + distance * Math.sin( radians );
}
Then you just call it:
rotateAround( rotateAmount, image, new Point( container.width/2, container.height/2 ) );
The last parameter distance you can pass whatever you like, so for example if I wanted a distance of the image vector length:
var dx:Number = spr.x - stage.stageWidth/2;
var dy:Number = spr.y - stage.stageHeight/2;
var dist:Number = Math.sqrt(dx * dx + dy * dy);
rotateAround( rotateAmount, image, new Point( container.width/2, container.height/2 ), dist );
Here's the solution I've found:
public static function rotateImageAroundCenterOfViewPort(image:Image, value:int):void
{
// Calculate rotation and shifts
var center:Point = new Point(image.parent.width / 2, image.parent.height / 2);
center = image.parent.localToGlobal(center);
center = image.globalToLocal(center);
var angle:Number = value - image.rotation;
var radians:Number = angle * (Math.PI / 180.0);
var shiftByX:Number = center.x;
var shiftByY:Number = center.y;
// Perform rotation
var matrix:Matrix = new Matrix();
matrix.translate(-shiftByX, -shiftByY);
matrix.rotate(radians);
matrix.translate(+shiftByX, +shiftByY);
matrix.concat(image.transform.matrix);
image.transform.matrix = matrix;
image.rotation = Math.round(image.rotation);
}
Teste only with the angles like 90, 180 etc. (I don't need any else).

How to draw a series of arc segments across line segments

I'm trying to draw a 'countdown' circle that will produce a visual clock, but don't like the resulting shape as it is not smooth. The code below draws and fills a series of triangles that approximate a circle when the variable 'numberOfSides' is sufficiently large, but this is both inefficient and produces an ugly 'circle.' What I would like to do is draw a series of arcs across the line segments but I don't know how to do it. Can anyone give me a prod in the right direction?
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600" frameRate="100" creationComplete="init()">
<mx:Script>
<![CDATA[
private var numberOfSides:int = 10;
private var angle:Number = 0;
private var lineLength:int = 100;
private var xStartingPos:int = 300;
private var yStartingPos:int = 300;
private var i:int = 90;//Start the drawing at 0 to 360;
private var speed:int = 100;
private var timer:Timer
private function init():void{
//uic.graphics.lineStyle(1);
uic.graphics.moveTo(xStartingPos,yStartingPos);
uic.graphics.beginFill(0xffff00);
timer = new Timer(speed, numberOfSides + 1);
timer.addEventListener(TimerEvent.TIMER,cont);
timer.start()
}
private function cont(event:TimerEvent):void{
angle = (Math.PI / numberOfSides * 2) * i;
var xAngle:Number = Math.sin(angle);
var yAngle:Number = Math.cos(angle);
var xResult:int = xAngle * lineLength;
var yResult:int = yAngle * lineLength;
uic.graphics.lineTo(xStartingPos + xResult, yStartingPos + (yResult*-1));
i++
}
]]>
</mx:Script>
<mx:Canvas id="uic"/>
</mx:Application>
This could be accomplished by drawing wedges using nl.funkymonkey.drawing.DrawingShapes:
Draw Wedge function:
public static function drawWedge(target:Graphics, x:Number, y:Number, radius:Number, arc:Number, startAngle:Number=0, yRadius:Number=0):void
{
if (yRadius == 0)
yRadius = radius;
target.moveTo(x, y);
var segAngle:Number, theta:Number, angle:Number, angleMid:Number, segs:Number, ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number;
if (Math.abs(arc) > 360)
arc = 360;
segs = Math.ceil(Math.abs(arc) / 45);
segAngle = arc / segs;
theta = -(segAngle / 180) * Math.PI;
angle = -(startAngle / 180) * Math.PI;
if (segs > 0)
{
ax = x + Math.cos(startAngle / 180 * Math.PI) * radius;
ay = y + Math.sin(-startAngle / 180 * Math.PI) * yRadius;
target.lineTo(ax, ay);
for (var i:int = 0; i < segs; ++i)
{
angle += theta;
angleMid = angle - (theta / 2);
bx = x + Math.cos(angle) * radius;
by = y + Math.sin(angle) * yRadius;
cx = x + Math.cos(angleMid) * (radius / Math.cos(theta / 2));
cy = y + Math.sin(angleMid) * (yRadius / Math.cos(theta / 2));
target.curveTo(cx, cy, bx, by);
}
target.lineTo(x, y);
}
}
Example implementation, animating countdown timer:
var value:Number = 0;
addEventListener(Event.ENTER_FRAME, frameHandler);
function frameHandler(event:Event):void
{
var g:Graphics = graphics;
g.clear();
value += 0.01;
g.lineStyle(1, 0x0000ff, 0.25);
g.beginFill(0x123456, 0.25);
drawWedge(g, 100, 100, 50, (360 * value) % 360);
g.endFill();
}
Any graphics line style or fill may be used; or, the fill could be used as a mask.
An other piece of code I found online:
http://flassari.is/2009/11/pie-mask-in-as3/
Changed this into:
var circleToMask:Sprite = new Sprite();
circleToMask.graphics.beginFill(0x004BA5DC); // color circle 0x00RRGGBB
circleToMask.graphics.drawCircle(0, 0, 50);
circleToMask.graphics.endFill();
addChildAt(circleToMask, 0);
var circleMask:Sprite = new Sprite();
circleToMask.x = (circleMask.x = 50);
circleToMask.y = (circleMask.y = 50);
circleToMask.mask = circleMask;
addChild(circleMask);
// inner circle
var circleTopMask:Sprite = new Sprite();
circleTopMask.graphics.beginFill(0x00FFFFFF); // color inner circle
circleTopMask.graphics.drawCircle(0, 0, 25);
circleTopMask.graphics.endFill();
addChild(circleTopMask);
circleTopMask.x = 50;
circleTopMask.y = 50;
// textfield in the center
var myFormat:TextFormat = new TextFormat();
myFormat.size = 18;
var myText:TextField = new TextField();
myText.defaultTextFormat = myFormat;
myText.text = "0%";
myText.autoSize = TextFieldAutoSize.CENTER;
myText.y = 50 - (myText.height/2);
addChild(myText);
var percentage:Number = 0;
var tper:Number = 0;
addEventListener(Event.ENTER_FRAME, function (_arg1:Event):void{
graphics.clear();
// Percentage should be between 0 and 1
tper = percentage < 0 ? 0 : (percentage > 1 ? 1 : percentage);
// Draw the masked circle
circleMask.graphics.clear();
circleMask.graphics.beginFill(0);
drawPieMask(circleMask.graphics, tper, 50, 0, 0, (-(Math.PI) / 2), 3);
circleMask.graphics.endFill();
// Increase percentage with margins so it appears to stop for a short while
percentage = (percentage + 0.01);
if (percentage > 1){
percentage = 0;
}
myText.text = ((percentage*100).toFixed(0))+"%";
})
function drawPieMask(graphics:Graphics, percentage:Number, radius:Number = 50, x:Number = 0, y:Number = 0, rotation:Number = 0, sides:int = 6):void {
// graphics should have its beginFill function already called by now
graphics.moveTo(x, y);
if (sides < 3) sides = 3; // 3 sides minimum
// Increase the length of the radius to cover the whole target
radius /= Math.cos(1/sides * Math.PI);
// Shortcut function
var lineToRadians:Function = function(rads:Number):void {
graphics.lineTo(Math.cos(rads) * radius + x, Math.sin(rads) * radius + y);
};
// Find how many sides we have to draw
var sidesToDraw:int = Math.floor(percentage * sides);
for (var i:int = 0; i <= sidesToDraw; i++)
lineToRadians((i / sides) * (Math.PI * 2) + rotation);
// Draw the last fractioned side
if (percentage * sides != sidesToDraw)
lineToRadians(percentage * (Math.PI * 2) + rotation);
}
The size of the resuling FLA should be 100 x 100 px.
Resulting in:

The text around the center of the circle along the radius

Please help to solve the geometric issue. I'm creating multiple sectors of the wheel. Now I need to be placed in every sector the text oriented from the center of the circle to the edge. But somehow, everything goes awry.
Here is the class of one sector:
package comps
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.filters.GlowFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Vector3D;
import flash.net.URLRequest;
import flash.text.AntiAliasType;
import flash.text.Font;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
public class Sector extends Sprite
{
private var imageX1:Number;
private var imageY1:Number;
private var imageX2:Number;
private var imageY2:Number;
[Embed(source="font/SEGOEUIB.TTF", fontFamily="SegoeUI", mimeType="application/x-font", embedAsCFF="false", advancedAntiAliasing="true")]
private var MoolEmbed:Class;
public function Sector(tip:String, rot:Number, centerX:Number, centerY:Number, innerRadius:Number, outerRadius:Number, startAngle:Number, arcAngle:Number, val:Number, color:uint, alpha:Number, url:String, value:Number, steps:int=20)
{
Font.registerFont(MoolEmbed);
var myArc:Sprite = new Sprite();
myArc.graphics.lineStyle(2);
myArc.graphics.beginFill(color, alpha);
drawSolidArc (myArc,centerX, centerY, innerRadius, outerRadius, startAngle, arcAngle, url, steps);
myArc.graphics.endFill();
this.addChild(myArc);
var myFormat:TextFormat = new TextFormat();
myFormat.size = 20;
myFormat.align = TextFormatAlign.CENTER;
myFormat.font = 'SegoeUI';
myFormat.bold = true;
var myText:TextField = new TextField();
myText.defaultTextFormat = myFormat;
myText.setTextFormat(myFormat);
myText.embedFonts = true;
myText.antiAliasType = AntiAliasType.ADVANCED;
myText.text = url;
this.addChild(myText);
myText.wordWrap = true;
myText.width = outerRadius - innerRadius;
myText.height = 20;
var sectorsNum:Number = rot/360;
textRotation(myText, rot*(sectorsNum-value) - rot*0.5, centerX, centerY);
myText.x = imageX1 + 0 * (imageX2 - imageX1);
myText.y = imageY1 + 0 * (imageY2 - imageY1);
//trace (myText.x + " " + myText.y);
var myGlow:GlowFilter = new GlowFilter();
myGlow.color = 0xFFFFFF;
myGlow.blurX = 20;
myGlow.blurY = 20;
myGlow.strength = 2;
myText.filters = [myGlow];
}
}
private function textRotation (target:TextField, angle:Number, centerX:Number, centerY:Number):void{
var point:Point=new Point(centerX + target.width/2, centerY + target.height/2);
var m:Matrix=target.transform.matrix;
m.tx -= point.x;
m.ty -= point.y;
m.rotate (angle*(Math.PI/180));
m.tx += point.x;
m.ty += point.y;
target.transform.matrix=m;
}
private function drawSolidArc (drawObj:Object, centerX:Number,centerY:Number,innerRadius:Number,outerRadius:Number,startAngle:Number,arcAngle:Number,url:String,steps:int=20):void {
var twoPI:Number = 2 * Math.PI;
var angleStep:Number = arcAngle/steps;
var angle:Number, i:int, endAngle:Number;
var xx:Number = centerX + Math.cos(startAngle * twoPI) * innerRadius;
var yy:Number = centerY + Math.sin(startAngle * twoPI) * innerRadius;
var xxInit:Number=xx;
var yyInit:Number=yy;
drawObj.graphics.moveTo(xx,yy);
for(i=1; i<=steps; i++) {
angle = (startAngle + i * angleStep) * twoPI;
xx = centerX + Math.cos(angle) * innerRadius;
yy = centerY + Math.sin(angle) * innerRadius;
drawObj.graphics.lineTo(xx,yy);
if (i==steps*0.5-1){
imageX1 = xx;
imageY1 = yy;
}
}
endAngle = startAngle + arcAngle;
for(i=0;i<=steps;i++) {
angle = (endAngle - i * angleStep) * twoPI;
xx = centerX + Math.cos(angle) * outerRadius;
yy = centerY + Math.sin(angle) * outerRadius;
drawObj.graphics.lineTo(xx,yy);
if (i==steps*0.5){
imageX2 = xx;
imageY2 = yy;
}
}
drawObj.graphics.lineTo(xxInit,yyInit);
}
}
}
And that's creating all the wheel:
sectorsNum = tarotAC.length;
for (var i:int = 0; i < sectorsNum; i++){
var arcAngle:Number = - 1/sectorsNum;
var startAngle:Number = arcAngle*i;
var arc:Sector = new Sector(FlexGlobals.topLevelApplication.mode, 360/sectorsNum, 0, 0, stageW*0.1, stageW*0.5, startAngle, arcAngle, Number(sectorsNum - i), tarotAC.getItemAt(i).color, 1, tarotAC.getItemAt(i).datas, tarotAC.getItemAt(i).url);
con.addChild(arc);
}
By looking at it, I would say your TextFields need to be moved up by their height. TextFields are created from the top and the coordinates you give them would fit better with the baseline. Maybe you could put your TextField in a Sprite, set the TextField's y to -height and then manipulate the container instead of the text itself:
var container:Sprite = new Sprite();
container.addChild(myText);
this.addChild(container);
myText.y = -myText.height;
textRotation(container, rot*(sectorsNum-value) - rot*0.5, centerX, centerY);
//you should of course modify textRotation's first parameter type
container.x = imageX1 + 0 * (imageX2 - imageX1);
container.y = imageY1 + 0 * (imageY2 - imageY1);

Drawing a circle Google Static Maps

I have a Google Maps Circle drawn on v3 api. When the user has plotted there circle (or polygon if they choose), they can save the data to the server. If the user has picked a radial search, the Centre coordinates and the radius in feet is stored to the database. This means when the user reloads his search, it can pull through the circle again (like below).
I'm having 1 problem, however, which is when the user selects what search they would like to use. It loads the polygon fine, if they drew a polygon, and if it's a circle it pulls through the marker on the center. However what I need is a function in static maps to draw a circle.
A bit late in the game, but nothing I found solved my issue (serverside php only, no javascript).
I ended up getting there in the end and have detailed my method here: http://jomacinc.com/map-radius/ and the short version is below.
This PHP function will return an encoded polyline string of lat/lng points in a circle around the specified point, and at the specified radius. The function requires Gabriel Svennerberg’s PHP polyline encoding class available here (http://www.svennerberg.com/examples/polylines/PolylineEncoder.php.txt).
function GMapCircle($Lat,$Lng,$Rad,$Detail=8){
$R = 6371;
$pi = pi();
$Lat = ($Lat * $pi) / 180;
$Lng = ($Lng * $pi) / 180;
$d = $Rad / $R;
$points = array();
$i = 0;
for($i = 0; $i <= 360; $i+=$Detail):
$brng = $i * $pi / 180;
$pLat = asin(sin($Lat)*cos($d) + cos($Lat)*sin($d)*cos($brng));
$pLng = (($Lng + atan2(sin($brng)*sin($d)*cos($Lat), cos($d)-sin($Lat)*sin($pLat))) * 180) / $pi;
$pLat = ($pLat * 180) /$pi;
$points[] = array($pLat,$pLng);
endfor;
require_once('PolylineEncoder.php');
$PolyEnc = new PolylineEncoder($points);
$EncString = $PolyEnc->dpEncode();
return $EncString['Points'];
}
You can now the use the above function to create a static map.
/* set some options */
$MapLat    = '-42.88188'; // latitude for map and circle center
$MapLng    = '147.32427'; // longitude as above
$MapRadius = 100;         // the radius of our circle (in Kilometres)
$MapFill   = 'E85F0E';    // fill colour of our circle
$MapBorder = '91A93A';    // border colour of our circle
$MapWidth  = 640;         // map image width (max 640px)
$MapHeight = 480;         // map image height (max 640px)
/* create our encoded polyline string */
$EncString = GMapCircle($MapLat,$MapLng, $MapRadius);
/* put together the static map URL */
$MapAPI = 'http://maps.google.com.au/maps/api/staticmap?';
$MapURL = $MapAPI.'center='.$MapLat.','.$MapLng.'&size='.$MapWidth.'x'.$MapHeight.'&maptype=roadmap&path=fillcolor:0x'.$MapFill.'33%7Ccolor:0x'.$MapBorder.'00%7Cenc:'.$EncString.'&sensor=false';
/* output an image tag with our map as the source */
echo '<img src="'.$MapURL.'" />'
function GMapCircle(lat,lng,rad,detail=8){
var uri = 'https://maps.googleapis.com/maps/api/staticmap?';
var staticMapSrc = 'center=' + lat + ',' + lng;
staticMapSrc += '&size=100x100';
staticMapSrc += '&path=color:0xff0000ff:weight:1';
var r = 6371;
var pi = Math.PI;
var _lat = (lat * pi) / 180;
var _lng = (lng * pi) / 180;
var d = (rad/1000) / r;
var i = 0;
for(i = 0; i <= 360; i+=detail) {
var brng = i * pi / 180;
var pLat = Math.asin(Math.sin(_lat) * Math.cos(d) + Math.cos(_lat) * Math.sin(d) * Math.cos(brng));
var pLng = ((_lng + Math.atan2(Math.sin(brng) * Math.sin(d) * Math.cos(_lat), Math.cos(d) - Math.sin(_lat) * Math.sin(pLat))) * 180) / pi;
pLat = (pLat * 180) / pi;
staticMapSrc += "|" + pLat + "," + pLng;
}
return uri + encodeURI(staticMapSrc);}
Javascript version
Based on the answer from Jomac, Here is a Java/Android version of the same code.
It uses the PolyUtil class from Google Maps Android API Utility Library to encode the path.
import android.location.Location;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.PolyUtil;
import java.util.ArrayList;
public class GoogleStaticMapsAPIServices
{
private static final double EARTH_RADIUS_KM = 6371;
private static String GOOGLE_STATIC_MAPS_API_KEY = "XXXXXXXXXXXXX";
public static String getStaticMapURL(Location location, int radiusMeters)
{
String pathString = "";
if (radiusMeters > 0)
{
// Add radius path
ArrayList<LatLng> circlePoints = getCircleAsPolyline(location, radiusMeters);
if (circlePoints.size() > 0)
{
String encodedPathLocations = PolyUtil.encode(circlePoints);
pathString = "&path=color:0x0000ffff%7Cweight:1%7Cfillcolor:0x0000ff80%7Cenc:" + encodedPathLocations;
}
}
String staticMapURL = "https://maps.googleapis.com/maps/api/staticmap?size=640x320&markers=color:red%7C" +
location.getLatitude() + "," + location.getLongitude() +
pathString +
"&key=" + GOOGLE_STATIC_MAPS_API_KEY;
return staticMapURL;
}
private static ArrayList<LatLng> getCircleAsPolyline(Location center, int radiusMeters)
{
ArrayList<LatLng> path = new ArrayList<>();
double latitudeRadians = center.getLatitude() * Math.PI / 180.0;
double longitudeRadians = center.getLongitude() * Math.PI / 180.0;
double radiusRadians = radiusMeters / 1000.0 / EARTH_RADIUS_KM;
double calcLatPrefix = Math.sin(latitudeRadians) * Math.cos(radiusRadians);
double calcLatSuffix = Math.cos(latitudeRadians) * Math.sin(radiusRadians);
for (int angle = 0; angle < 361; angle += 10)
{
double angleRadians = angle * Math.PI / 180.0;
double latitude = Math.asin(calcLatPrefix + calcLatSuffix * Math.cos(angleRadians));
double longitude = ((longitudeRadians + Math.atan2(Math.sin(angleRadians) * Math.sin(radiusRadians) * Math.cos(latitudeRadians), Math.cos(radiusRadians) - Math.sin(latitudeRadians) * Math.sin(latitude))) * 180) / Math.PI;
latitude = latitude * 180.0 / Math.PI;
path.add(new LatLng(latitude, longitude));
}
return path;
}
}
I think it is not possible to draw a circle on a static Google map. You would need to approximate the circle by a polyline (best in encoded format). This has already been mentioned in Stackoverflow and it is demonstrated e.g. by Free Map Tools.
Sharing my C# version
private string GMapCircle(double lat, double lng, double rad, int detail = 8)
{
const string uri = "https://maps.googleapis.com/maps/api/staticmap?";
var staticMapSrc = "center=" + lat + "," + lng;
staticMapSrc += "&zoom=16";
staticMapSrc += "&maptype=satellite";
staticMapSrc += "&key=[YOURKEYHERE]";
staticMapSrc += "&size=640x426";
staticMapSrc += "&path=color:0xff0000ff:weight:1";
const int r = 6371;
const double pi = Math.PI;
var latAux = (lat * pi) / 180;
var longAux = (lng * pi) / 180;
var d = (rad / 1000) / r;
var i = 0;
if (rad > 0)
{
for (i = 0; i <= 360; i += detail)
{
var brng = i * pi / 180;
var pLat = Math.Asin(Math.Sin(latAux) * Math.Cos(d) + Math.Cos(latAux) * Math.Sin(d) * Math.Cos(brng));
var pLng = ((longAux + Math.Atan2(Math.Sin(brng) * Math.Sin(d) * Math.Cos(latAux), Math.Cos(d) - Math.Sin(latAux) * Math.Sin(pLat))) * 180) / pi;
pLat = (pLat * 180) / pi;
staticMapSrc += "|" + pLat + "," + pLng;
}
}
else
{
//TODO - Add marker
}
return uri + staticMapSrc;
}
This solution uses the significantly more versatile Canvas API to draw over the map image . All code is in Typescript, so simply remove type declarations if you're using Javascript.
ADVANGES OF USING CANVAS:
Its easier to draw shapes on.
Those shapes can also be revised without re-requesting the map image from Google.
The drawing 'layer' can be serialized independently of the underlying map image.
USAGE:
// DEFINE BASIC VARIABLES
const latitude: -34.3566871,
const longitude: 18.4967666
const mapZoom = 12;
const imageWidth: 100;
const imageHeight: 100;
// INVOKE UTILITY FUNCTION
savePlaceImage({
// SET BASIC PROPS
latitude,
longitude,
mapZoom,
imageWidth,
imageHeight,
fileName: 'Cape Point',
// DRAW IMAGE USING CANVAS API
draw: ctx => {
// draw location as dot
ctx.fillStyle = '#FF3366';
ctx.beginPath();
ctx.arc(imageWidth / 2, imageHeight / 2, 10, 0, 2 * Math.PI);
ctx.fill();
// draw circle around location with 1 kilometer radius
ctx.strokeStyle = '#0000FF';
ctx.beginPath();
ctx.arc(imageWidth / 2, imageHeight / 2, pixelsPerMeter(latitude) * 1000, 0, 2 * Math.PI);
ctx.stroke();
}
})
UTILITIES:
function savePlaceImage(
config: {
latitude: number,
longitude: number,
mapZoom: number,
imageWidth: number,
imageHeight: number,
fileName: string,
draw: (ctx: CanvasRenderingContext2D) => void,
},
) {
// DOWNLOAD MAP IMAGE FROM GOOGLE'S STATIC MAPS API AS A BLOB
return from(axios.get<Blob>(`https://maps.googleapis.com/maps/api/staticmap`, {
params: {
key: GOOGLE_MAPS_API_KEY,
size: `${config.imageWidth}x${config.imageHeight}`,
zoom: `${config.mapZoom}`,
center: `${config.latitude},${config.longitude}`,
style: 'feature:all|element:labels|visibility:off',
},
responseType: 'blob',
// CONVERT BLOB TO BASE64 ENCODED STRING
}).then(response => {
const reader = new FileReader();
reader.readAsDataURL(response.data);
return new Promise<string>(resolve => reader.onloadend = () => resolve(reader.result as string));
// CREATE HTML IMG ELEMENT, SET IT'S SRC TO MAP IMAGE, AND WAIT FOR IT TO LOAD
}).then(response => {
const image = document.createElement('img');
image.src = response;
return new Promise<HTMLImageElement>(resolve => image.onload = () => resolve(image));
// CREATE HTML CANVAS ELEMENT, THEN DRAW ON TOP OF CANVAS USING CANVAS API, THEN CONVERT TO BLOB
}).then(image => {
const canvas = document.createElement('canvas');
canvas.width = config.imageWidth;
canvas.height = config.imageHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
config.draw(ctx);
return new Promise<Blob>(resolve => canvas.toBlob(blob => resolve(blob)));
// ATTACH BLOB TO HTML FORM WHICH CONVERTS IT TO A FILE TO BE POSTED, THEN SEND FILE TO SERVER
}).then(blob => {
const form = new FormData();
form.append('blob', blob, `${config.fileName}.png`);
const file = form.get('blob') as File;
return axios.post<{ file }>('https://www.my-api.com/save-image', form);
}));
}
function pixelsPerMeter(latitude: number) {
const radiusOfEarthInKilometers = 6371;
return Math.cos(latitude * Math.PI / 180) * 2 * Math.PI * radiusOfEarthInKilometers / (256 * Math.pow(2, 12));
}
python version, polyline library used for encoding the polygon
import math, polyline
def g_map_circle(lat,lng,radius,detail=8):
points = []
r = 6371
pi = math.pi
_lat = (lat * pi) /180
_lng = (lng * pi) /180
d = radius / r
i = 0
while i <= 360:
i = i + detail
brng = i * pi /180
p_lat = math.asin(math.sin(_lat) * math.cos(d) + math.cos(_lat) * math.sin(d) * math.cos(brng));
p_lng = (_lng + math.atan2(math.sin(brng) * math.sin(d) * math.cos(_lat), math.cos(d) - math.sin(_lat) * math.sin(p_lat))) * 180 / pi
p_lat = (p_lat * 180) /pi
points.append((p_lat,p_lng))
return polyline.encode(points)