Related
Uncaught SecurityError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D':
The canvas has been tainted by cross-origin data
I am getting above error only in chrome. My code(following) works fine in mozilla. I have not corss domain issue. So why could i get above error in chrome?
var wheel_canvas, wheel_context, can_w, can_h, wheel_grd;
var LARGE_RADIUS = 160;
var SMALL_RADIUS = 120;
var wheel_canvas = document.getElementById('wheel_canvas');
var wheel_context = wheel_canvas.getContext('2d');
var can_w = $(wheel_canvas).width();
var can_h = $(wheel_canvas).height();
var centerX = can_w / 2, centerY = can_h / 2;
var wheel_grd = null;
test('#FF0000');
function test(hex)
{
$('#clrpicker').css({'left': 210, 'top': 210 });
inverted = hexBW(hex);
$('#color_val_show').val(hex);
current_color_hex_val_selected = hex;
$('#color_val_show').css({'color':inverted,'background':hex});
fillGradientCirle(hex);
}
function fillGradientCirle(hex)
{
wheel_context.rect(0, 0, can_w, can_h);
wheel_grd = wheel_context.createLinearGradient(1, 1, 0, can_w, can_h);
wheel_grd.addColorStop(1, '#ffffff');
wheel_grd.addColorStop(0.5, hex);
wheel_grd.addColorStop(0, '#000000');
wheel_context.fillStyle = wheel_grd;
wheel_context.beginPath();
wheel_context.arc(centerX, centerY, LARGE_RADIUS, 0, 2 * Math.PI);
wheel_context.fill();
}
$(wheel_canvas).click(function (e)
{
$.event.fix(e);
x = e.pageX;
y = e.pageY;
can_p = $(this).offset();
x = x - $(document).scrollLeft() - can_p.left;
// Fixed typo
y = y - $(document).scrollTop() - can_p.top;
if (Math.sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)) < SMALL_RADIUS) return; // Precaution
$('#wheel_picker').css({ 'left': x-8 + 'px', 'top': y-8 + 'px' });
var data = wheel_context.getImageData(x, y, 1, 1).data;
pixelData = data;
rgbString = 'rgb(' + pixelData[0] + ', ' + pixelData[1] + ', ' + pixelData[2] + ')';
hex = rgb2hex(rgbString);
inverted = hexBW(hex);
$('#color_val_show').val(hex);
current_color_hex_val_selected = hex;
$('#color_val_show').css({'color':inverted,'background':hex});
$('#feedback').html("Coordinates : " + x + "," + y + " Color: " + hex);
});
If this is all the code and you don't use any cross-origin images then the only error that stand out is this line:
wheel_grd = wheel_context.createLinearGradient(1, 1, 0, can_w, can_h);
This should only have four arguments not five. The last argument may trip out Chrome in some way.
Try by changing it to:
// x0 y0 x1 y1
wheel_grd = wheel_context.createLinearGradient(1, 0, can_w, can_h);
Other errors you can consider looking at:
You are declaring the variables twice - the first var line in the example code is unnecessary
You are reading CSS size of canvas element, not its bitmap size (they are not the same).
To read proper dimension of canvas (unless you intended to read the CSS size for some reason) you need to use the following instead:
var can_w = wheel_canvas.width; //$(wheel_canvas).width();
var can_h = wheel_canvas.height; //$(wheel_canvas).height();
or if you insist on using jQuery:
var can_w = $(wheel_canvas).prop('width');
var can_h = $(wheel_canvas).prop('height');
Hope this helps!
For testing, you can also use --allow-file-access-from-files as a command-line argument to Chrome; this will allow you to use local files.
I use following codes in order to move a picture in canvas for my speedometer.
var meter = new Image,
needle = new Image;
window.onload = function () {
var c = document.getElementsByTagName('canvas')[0];
var ctx = c.getContext('2d');
setInterval(function () {
ctx.save();
ctx.clearRect(0, 0, c.width, c.height);
ctx.translate(c.width / 2, c.height / 2);
ctx.drawImage(meter, -165, -160);
ctx.rotate((x * Math.PI / 180);
/ x degree
ctx.drawImage( needle, -13, -121.5 );
ctx.restore();
},50);
};
meter.src = 'meter.png';
needle.src = 'needle.png';
}
However I want to move the needle slowly to the degree which I entered such as speedtest webpages. Any idea?
Thanks.
Something like this should work:
var meter = new Image,
needle = new Image;
window.onload = function () {
var c = document.getElementsByTagName('canvas')[0],
ctx = c.getContext('2d'),
x, // Current angle
xTarget, // Target angle.
step = 1; // Angle change step size.
setInterval(function () {
if(Math.abs(xTarget - x) < step){
x = xTarget; // If x is closer to xTarget than the step size, set x to xTarget.
}else{
x += (xTarget > x) ? step : // Increment x to approach the target.
(xTarget < x) ? -step : // (Or substract 1)
0;
}
ctx.save();
ctx.clearRect(0, 0, c.width, c.height);
ctx.translate(c.width / 2, c.height / 2);
ctx.drawImage(meter, -165, -160);
ctx.rotate((x * Math.PI / 180); // x degree
ctx.drawImage( needle, -13, -121.5 );
ctx.restore();
},50);
};
dial.src = 'meter.png';
needle.src = 'needle.png';
}
I'm using a shorthand if / else here to determine whether to add 1 to x, substract 1, or do nothing. Functionally, this is the same as:
if(xTarget > x){
x += step;
}else if(xTarget < x){
x += -step;
}else{
x += 0;
}
But it's shorter, and in my opinion, just as easy to read, once you know what a shorthand if (ternary operator) looks like.
Please keep in mind that this code assumes x is a integer value (So, not a float, just a rounded int).
UPDATE: Once I got this demo working... holy smokes, it's SLOW, like 12-16 seconds for only a level 2 render (when image is around 1000x2000 pixels). This is not even worth bothering with.
I found this really awesome and hopeful looking code in the top answer here: Resizing an image in an HTML5 canvas
//returns a function that calculates lanczos weight
function lanczosCreate(lobes){
return function(x){
if (x > lobes)
return 0;
x *= Math.PI;
if (Math.abs(x) < 1e-16)
return 1
var xx = x / lobes;
return Math.sin(x) * Math.sin(xx) / x / xx;
}
}
//elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes){
this.canvas = elem;
elem.width = img.width;
elem.height = img.height;
elem.style.display = "none";
this.ctx = elem.getContext("2d");
this.ctx.drawImage(img, 0, 0);
this.img = img;
this.src = this.ctx.getImageData(0, 0, img.width, img.height);
this.dest = {
width: sx,
height: Math.round(img.height * sx / img.width),
};
this.dest.data = new Array(this.dest.width * this.dest.height * 3);
this.lanczos = lanczosCreate(lobes);
this.ratio = img.width / sx;
this.rcp_ratio = 2 / this.ratio;
this.range2 = Math.ceil(this.ratio * lobes / 2);
this.cacheLanc = {};
this.center = {};
this.icenter = {};
setTimeout(this.process1, 0, this, 0);
}
thumbnailer.prototype.process1 = function(self, u){
self.center.x = (u + 0.5) * self.ratio;
self.icenter.x = Math.floor(self.center.x);
for (var v = 0; v < self.dest.height; v++) {
self.center.y = (v + 0.5) * self.ratio;
self.icenter.y = Math.floor(self.center.y);
var a, r, g, b;
a = r = g = b = 0;
for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
if (i < 0 || i >= self.src.width)
continue;
var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
if (!self.cacheLanc[f_x])
self.cacheLanc[f_x] = {};
for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
if (j < 0 || j >= self.src.height)
continue;
var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
if (self.cacheLanc[f_x][f_y] == undefined)
self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
weight = self.cacheLanc[f_x][f_y];
if (weight > 0) {
var idx = (j * self.src.width + i) * 4;
a += weight;
r += weight * self.src.data[idx];
g += weight * self.src.data[idx + 1];
b += weight * self.src.data[idx + 2];
}
}
}
var idx = (v * self.dest.width + u) * 3;
self.dest.data[idx] = r / a;
self.dest.data[idx + 1] = g / a;
self.dest.data[idx + 2] = b / a;
}
if (++u < self.dest.width)
setTimeout(self.process1, 0, self, u);
else
setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self){
self.canvas.width = self.dest.width;
self.canvas.height = self.dest.height;
self.ctx.drawImage(self.img, 0, 0);
self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
var idx, idx2;
for (var i = 0; i < self.dest.width; i++) {
for (var j = 0; j < self.dest.height; j++) {
idx = (j * self.dest.width + i) * 3;
idx2 = (j * self.dest.width + i) * 4;
self.src.data[idx2] = self.dest.data[idx];
self.src.data[idx2 + 1] = self.dest.data[idx + 1];
self.src.data[idx2 + 2] = self.dest.data[idx + 2];
}
}
self.ctx.putImageData(self.src, 0, 0);
self.canvas.style.display = "block";
}
...
img.onload = function() {
var canvas = document.createElement("canvas");
new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
//but feel free to raise it up to 8. Your client will appreciate
//that the program makes full use of his machine.
document.body.appendChild(canvas);
}
However, this implementation loads an image and renders it, end of story.
I have been trying to re-implement this code so that it does the filtering every time an existing canvas is scaled (think, zooming in and out of an image or document) without having to load a new image or create a new canvas.
How can I adapt it to work this way? Or is that even possible?
What you want to do is something like a singleton to reuse your canvas object. This will let you save the cost of create a new canvas object each time and you will reuse the same object
function getCanvas(){
var canvas;
if (typeof canvas === "undefined"){ canvas = document.createElement("canvas");}
return canvas;
}
img.onload = function() {
var canvas = getCanvas("canvas");
.... THE REST OF YOUR CODE .......
}
.
However this is not what slows your code, image scaling Algorithms are really heavy algorithms with intensive cpu use "usually make use of gpu acceleration at a really low level", and use advanced techniques like multiple bufferr and so others. here is a interesting tutorial in java.net on how image scaling works, it is in java but you can interpolate to any language.
Javascript is not ready for this techniques, so I recommend you to use the transformations available in the canvas api, as in the tutorial you read the efficient way is using the canvas2Dcontext.
var ctx = canvas.getContext("2d");
ctx.scale(2,2);
How to get bounds in degrees of google static map which has been returned, for example, for following request
http://maps.googleapis.com/maps/api/staticmap?center=0.0,0.0&zoom=10&size=640x640&sensor=false
As I know, full Earth map is 256x256 image. This means that n vertical pixels contain x degrees, but n horizontal pixels contain 2x degrees. Right?
As google says
center defines the center of the map, equidistant from all edges of the map. As I understood equidistant in pixels (or in degrees?). And each succeeding zoom level doubles the precision in both horizontal and vertical dimensions.
So, I can find delta value of Longitude of map for each zoom value as:
dLongitude = (HorizontalMapSizeInPixels / 256 ) * ( 360 / pow(2, zoom) );
Same calculations for Latitude:
dLatitude = (VerticalMapSizeInPixels / 256 ) * ( 180 / pow(2, zoom) );
VerticalMapSizeInPixels and HorizontalMapSizeInPixels are parameters of map size in URL.
It's good to calculate delta value of Longitude, but for Latitude it is wrong. I cannot find delta value of Latitude, there is some delta error.
As I know, full Earth map is 256x256 image.
Yes.
This means that n vertical pixels contain x degrees, but n horizontal
pixels contain 2x degrees. Right?
No. One pixel will represent varying amounts of latitude depending on the latitude. One pixel at the Equator represents less latitude than one pixel near the poles.
The corners of the map will depend on center, zoom level and map size, and you'd need to use the Mercator projection to calculate them.
If you don't want to load the full API, here's a MercatorProjection object:
var MERCATOR_RANGE = 256;
function bound(value, opt_min, opt_max) {
if (opt_min != null) value = Math.max(value, opt_min);
if (opt_max != null) value = Math.min(value, opt_max);
return value;
}
function degreesToRadians(deg) {
return deg * (Math.PI / 180);
}
function radiansToDegrees(rad) {
return rad / (Math.PI / 180);
}
function MercatorProjection() {
this.pixelOrigin_ = new google.maps.Point( MERCATOR_RANGE / 2, MERCATOR_RANGE / 2);
this.pixelsPerLonDegree_ = MERCATOR_RANGE / 360;
this.pixelsPerLonRadian_ = MERCATOR_RANGE / (2 * Math.PI);
};
MercatorProjection.prototype.fromLatLngToPoint = function(latLng, opt_point) {
var me = this;
var point = opt_point || new google.maps.Point(0, 0);
var origin = me.pixelOrigin_;
point.x = origin.x + latLng.lng() * me.pixelsPerLonDegree_;
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world tile.
var siny = bound(Math.sin(degreesToRadians(latLng.lat())), -0.9999, 0.9999);
point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * -me.pixelsPerLonRadian_;
return point;
};
MercatorProjection.prototype.fromPointToLatLng = function(point) {
var me = this;
var origin = me.pixelOrigin_;
var lng = (point.x - origin.x) / me.pixelsPerLonDegree_;
var latRadians = (point.y - origin.y) / -me.pixelsPerLonRadian_;
var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2);
return new google.maps.LatLng(lat, lng);
};
//pixelCoordinate = worldCoordinate * Math.pow(2,zoomLevel)
You can save that to a separate file, for example "MercatorProjection.js", and then include it in your application.
<script src="MercatorProjection.js"></script>
With the above file loaded, the following function calculates the SW and NE corners of a map of a given size and at a given zoom.
function getCorners(center,zoom,mapWidth,mapHeight){
var scale = Math.pow(2,zoom);
var centerPx = proj.fromLatLngToPoint(center);
var SWPoint = {x: (centerPx.x -(mapWidth/2)/ scale) , y: (centerPx.y + (mapHeight/2)/ scale)};
var SWLatLon = proj.fromPointToLatLng(SWPoint);
alert('SW: ' + SWLatLon);
var NEPoint = {x: (centerPx.x +(mapWidth/2)/ scale) , y: (centerPx.y - (mapHeight/2)/ scale)};
var NELatLon = proj.fromPointToLatLng(NEPoint);
alert(' NE: '+ NELatLon);
}
and you'd call it like this:
var proj = new MercatorProjection();
var G = google.maps;
var centerPoint = new G.LatLng(49.141404, -121.960988);
var zoom = 10;
getCorners(centerPoint,zoom,640,640);
Thanks Marcelo for your answer. It has been quite helpful. In case anyone would be interested, here the Python version of the code (a rough translation of the PHP code, probably not as pythonic as it could be):
from __future__ import division
import math
MERCATOR_RANGE = 256
def bound(value, opt_min, opt_max):
if (opt_min != None):
value = max(value, opt_min)
if (opt_max != None):
value = min(value, opt_max)
return value
def degreesToRadians(deg) :
return deg * (math.pi / 180)
def radiansToDegrees(rad) :
return rad / (math.pi / 180)
class G_Point :
def __init__(self,x=0, y=0):
self.x = x
self.y = y
class G_LatLng :
def __init__(self,lt, ln):
self.lat = lt
self.lng = ln
class MercatorProjection :
def __init__(self) :
self.pixelOrigin_ = G_Point( MERCATOR_RANGE / 2, MERCATOR_RANGE / 2)
self.pixelsPerLonDegree_ = MERCATOR_RANGE / 360
self.pixelsPerLonRadian_ = MERCATOR_RANGE / (2 * math.pi)
def fromLatLngToPoint(self, latLng, opt_point=None) :
point = opt_point if opt_point is not None else G_Point(0,0)
origin = self.pixelOrigin_
point.x = origin.x + latLng.lng * self.pixelsPerLonDegree_
# NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
# 89.189. This is about a third of a tile past the edge of the world tile.
siny = bound(math.sin(degreesToRadians(latLng.lat)), -0.9999, 0.9999)
point.y = origin.y + 0.5 * math.log((1 + siny) / (1 - siny)) * - self.pixelsPerLonRadian_
return point
def fromPointToLatLng(self,point) :
origin = self.pixelOrigin_
lng = (point.x - origin.x) / self.pixelsPerLonDegree_
latRadians = (point.y - origin.y) / -self.pixelsPerLonRadian_
lat = radiansToDegrees(2 * math.atan(math.exp(latRadians)) - math.pi / 2)
return G_LatLng(lat, lng)
#pixelCoordinate = worldCoordinate * pow(2,zoomLevel)
def getCorners(center, zoom, mapWidth, mapHeight):
scale = 2**zoom
proj = MercatorProjection()
centerPx = proj.fromLatLngToPoint(center)
SWPoint = G_Point(centerPx.x-(mapWidth/2)/scale, centerPx.y+(mapHeight/2)/scale)
SWLatLon = proj.fromPointToLatLng(SWPoint)
NEPoint = G_Point(centerPx.x+(mapWidth/2)/scale, centerPx.y-(mapHeight/2)/scale)
NELatLon = proj.fromPointToLatLng(NEPoint)
return {
'N' : NELatLon.lat,
'E' : NELatLon.lng,
'S' : SWLatLon.lat,
'W' : SWLatLon.lng,
}
Usage :
>>> import MercatorProjection
>>> centerLat = 49.141404
>>> centerLon = -121.960988
>>> zoom = 10
>>> mapWidth = 640
>>> mapHeight = 640
>>> centerPoint = MercatorProjection.G_LatLng(centerLat, centerLon)
>>> corners = MercatorProjection.getCorners(centerPoint, zoom, mapWidth, mapHeight)
>>> corners
{'E': -65.710988,
'N': 74.11120692972199,
'S': 0.333879313530149,
'W': -178.210988}
>>> mapURL = "http://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=%d&size=%dx%d&scale=2&maptype=roadmap&sensor=false"%(centerLat,centerLon,zoom,mapWidth,mapHeight)
>>> mapURL
http://maps.googleapis.com/maps/api/staticmap?center=49.141404,-121.960988&zoom=10&size=640x640&scale=2&maptype=roadmap&sensor=false'
Simple Python Version
Having spent a long time on a similar problem and using this thread for help, here is a simplified Python version of Marcelo (and Jmague)'s code:
import math
import requests
def latLngToPoint(mapWidth, mapHeight, lat, lng):
x = (lng + 180) * (mapWidth/360)
y = ((1 - math.log(math.tan(lat * math.pi / 180) + 1 / math.cos(lat * math.pi / 180)) / math.pi) / 2) * mapHeight
return(x, y)
def pointToLatLng(mapWidth, mapHeight, x, y):
lng = x / mapWidth * 360 - 180
n = math.pi - 2 * math.pi * y / mapHeight
lat = (180 / math.pi * math. atan(0.5 * (math.exp(n) - math.exp(-n))))
return(lat, lng)
def getImageBounds(mapWidth, mapHeight, xScale, yScale, lat, lng):
centreX, centreY = latLngToPoint(mapWidth, mapHeight, lat, lng)
southWestX = centreX - (mapWidth/2)/ xScale
southWestY = centreY + (mapHeight/2)/ yScale
SWlat, SWlng = pointToLatLng(mapWidth, mapHeight, southWestX, southWestY)
northEastX = centreX + (mapWidth/2)/ xScale
northEastY = centreY - (mapHeight/2)/ yScale
NElat, NElng = pointToLatLng(mapWidth, mapHeight, northEastX, northEastY)
return[SWlat, SWlng, NElat, NElng]
lat = 37.806716
lng = -122.477702
zoom = 16
picHeight = 640 #Resulting image height in pixels (x2 if scale parameter is set to 2)
picWidth = 640
mapHeight = 256 #Original map size - specific to Google Maps
mapWidth = 256
xScale = math.pow(2, zoom) / (picWidth/mapWidth)
yScale = math.pow(2, zoom) / (picHeight/mapWidth)
corners = getImageBounds(mapWidth, mapHeight, xScale, yScale, lat, lng)
Here I have used x and y to represent pixel values and lat lng as, Latitude and Longitude. lat, lng, zoom, picHeight and picWidth can all be changed to your specific use case. Changing the scale/ maptype etc will not affect this calculation.
I used this code to tile Static Maps images with no gaps/ overlap. If you want to see more of it in use/ how it can work in that sense there is more information on my GitHub.
Here is a line by line translation of Marcelo's code in PHP, which can probably be cleaned up a bit. Works great! Thanks to Marcelo for doing the hard part.
define("MERCATOR_RANGE", 256);
function degreesToRadians($deg) {
return $deg * (M_PI / 180);
}
function radiansToDegrees($rad) {
return $rad / (M_PI / 180);
}
function bound($value, $opt_min, $opt_max) {
if ($opt_min != null) $value = max($value, $opt_min);
if ($opt_max != null) $value = min($value, $opt_max);
return $value;
}
class G_Point {
public $x,$y;
function G_Point($x=0, $y=0){
$this->x = $x;
$this->y = $y;
}
}
class G_LatLng {
public $lat,$lng;
function G_LatLng($lt, $ln){
$this->lat = $lt;
$this->lng = $ln;
}
}
class MercatorProjection {
private $pixelOrigin_, $pixelsPerLonDegree_, $pixelsPerLonRadian_;
function MercatorProjection() {
$this->pixelOrigin_ = new G_Point( MERCATOR_RANGE / 2, MERCATOR_RANGE / 2);
$this->pixelsPerLonDegree_ = MERCATOR_RANGE / 360;
$this->pixelsPerLonRadian_ = MERCATOR_RANGE / (2 * M_PI);
}
public function fromLatLngToPoint($latLng, $opt_point=null) {
$me = $this;
$point = $opt_point ? $opt_point : new G_Point(0,0);
$origin = $me->pixelOrigin_;
$point->x = $origin->x + $latLng->lng * $me->pixelsPerLonDegree_;
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world tile.
$siny = bound(sin(degreesToRadians($latLng->lat)), -0.9999, 0.9999);
$point->y = $origin->y + 0.5 * log((1 + $siny) / (1 - $siny)) * -$me->pixelsPerLonRadian_;
return $point;
}
public function fromPointToLatLng($point) {
$me = $this;
$origin = $me->pixelOrigin_;
$lng = ($point->x - $origin->x) / $me->pixelsPerLonDegree_;
$latRadians = ($point->y - $origin->y) / -$me->pixelsPerLonRadian_;
$lat = radiansToDegrees(2 * atan(exp($latRadians)) - M_PI / 2);
return new G_LatLng($lat, $lng);
}
//pixelCoordinate = worldCoordinate * pow(2,zoomLevel)
}
function getCorners($center, $zoom, $mapWidth, $mapHeight){
$scale = pow(2, $zoom);
$proj = new MercatorProjection();
$centerPx = $proj->fromLatLngToPoint($center);
$SWPoint = new G_Point($centerPx->x-($mapWidth/2)/$scale, $centerPx->y+($mapHeight/2)/$scale);
$SWLatLon = $proj->fromPointToLatLng($SWPoint);
$NEPoint = new G_Point($centerPx->x+($mapWidth/2)/$scale, $centerPx->y-($mapHeight/2)/$scale);
$NELatLon = $proj->fromPointToLatLng($NEPoint);
return array(
'N' => $NELatLon->lat,
'E' => $NELatLon->lng,
'S' => $SWLatLon->lat,
'W' => $SWLatLon->lng,
);
}
Usage:
$centerLat = 49.141404;
$centerLon = -121.960988;
$zoom = 10;
$mapWidth = 640;
$mapHeight = 640;
$centerPoint = new G_LatLng($centerLat, $centerLon);
$corners = getCorners($centerPoint, $zoom, $mapWidth, $mapHeight);
$mapURL = "http://maps.googleapis.com/maps/api/staticmap?center={$centerLat},{$centerLon}&zoom={$zoom}&size={$mapWidth}x{$mapHeight}&scale=2&maptype=roadmap&sensor=false";
Here is translation to Delphi/Pascal with some optimizations to correspond more strict Pascal language and memory management.
unit Mercator.Google.Maps;
interface
uses System.Math;
type TG_Point = class(TObject)
private
Fx: integer;
Fy: integer;
public
property x: integer read Fx write Fx;
property y: integer read Fy write Fy;
constructor Create(Ax: integer = 0; Ay: integer = 0);
end;
type TG_LatLng = class(TObject)
private
FLat: double;
FLng: double;
public
property Lat: double read FLat write FLat;
property Lng: double read FLng write FLng;
constructor Create(ALat: double; ALng: double);
end;
type TMercatorProjection = class(TObject)
private
pixelOrigin_: TG_Point;
pixelsPerLonDegree_, pixelsPerLonRadian_: double;
function degreesToRadians(deg: double): double;
function radiansToDegrees(rad: double): double;
function bound(value: double; opt_min: double; opt_max: double): double;
public
constructor Create;
procedure fromLatLngToPoint(latLng: TG_LatLng; var point: TG_Point);
procedure fromPointToLatLng(point: TG_point; var latLng: TG_LatLng);
procedure getCorners(center: TG_LatLng; zoom: integer; mapWidth: integer; mapHeight: integer;
var NELatLon: TG_LatLng; var SWLatLon: TG_LatLng);
end;
implementation
const MERCATOR_RANGE = 256;
constructor TG_Point.Create(Ax: Integer = 0; Ay: Integer = 0);
begin
inherited Create;
Fx := Ax;
Fy := Ay
end;
// **************
constructor TG_LatLng.Create(ALat: double; ALng: double);
begin
inherited Create;
FLat := ALat;
FLng := ALng
end;
// **************
constructor TMercatorProjection.Create;
begin
inherited Create;
pixelOrigin_ := TG_Point.Create( Round(MERCATOR_RANGE / 2), Round(MERCATOR_RANGE / 2));
pixelsPerLonDegree_ := MERCATOR_RANGE / 360;
pixelsPerLonRadian_ := MERCATOR_RANGE / (2 * PI);
end;
// Translate degrees to radians
function TMercatorProjection.degreesToRadians(deg: double): double;
begin
Result := deg * (PI / 180);
end;
// Translate radians to degrees
function TMercatorProjection.radiansToDegrees(rad: double): double;
begin
Result := rad / (PI / 180);
end;
// keep value insid defined bounds
function TMercatorProjection.bound(value: double; opt_min: double; opt_max: double): double;
begin
if Value < opt_min then Result := opt_min
else if Value > opt_max then Result := opt_max
else Result := Value;
end;
procedure TMercatorProjection.fromLatLngToPoint(latLng: TG_LatLng; var point: TG_Point);
var
siny: double;
begin
if Assigned(point) then
begin
point.x := Round(pixelOrigin_.x + latLng.lng * pixelsPerLonDegree_);
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world tile.
siny := bound(sin(degreesToRadians(latLng.lat)), -0.9999, 0.9999);
point.y := Round(pixelOrigin_.y + 0.5 * ln((1 + siny) / (1 - siny)) * -pixelsPerLonRadian_);
end;
end;
procedure TMercatorProjection.fromPointToLatLng(point: TG_point; var latLng: TG_LatLng);
var
latRadians: double;
begin
if Assigned(latLng) then
begin
latLng.lng := (point.x - pixelOrigin_.x) / pixelsPerLonDegree_;
latRadians := (point.y - pixelOrigin_.y) / -pixelsPerLonRadian_;
latLng.lat := radiansToDegrees(2 * arctan(exp(latRadians)) - PI / 2);
end;
end;
//pixelCoordinate = worldCoordinate * pow(2,zoomLevel)
procedure TMercatorProjection.getCorners(center: TG_LatLng; zoom: integer; mapWidth: integer; mapHeight: integer;
var NELatLon: TG_LatLng; var SWLatLon: TG_LatLng);
var
scale: double;
centerPx, SWPoint, NEPoint: TG_Point;
begin
scale := power(2, zoom);
centerPx := TG_Point.Create(0, 0);
try
fromLatLngToPoint(center, centerPx);
SWPoint := TG_Point.Create(Round(centerPx.x-(mapWidth/2)/scale), Round(centerPx.y+(mapHeight/2)/scale));
NEPoint := TG_Point.Create(Round(centerPx.x+(mapWidth/2)/scale), Round(centerPx.y-(mapHeight/2)/scale));
try
fromPointToLatLng(SWPoint, SWLatLon);
fromPointToLatLng(NEPoint, NELatLon);
finally
SWPoint.Free;
NEPoint.Free;
end;
finally
centerPx.Free;
end;
end;
end.
Usage example:
with TMercatorProjection.Create do
try
CLatLon := TG_LatLng.Create(Latitude, Longitude);
SWLatLon := TG_LatLng.Create(0,0);
NELatLon := TG_LatLng.Create(0,0);
try
getCorners(CLatLon, Zoom,
MapWidth, MapHeight,
SWLatLon, NELatLon);
finally
ShowMessage('SWLat='+FloatToStr(SWLatLon.Lat)+' | SWLon='+FloatToStr(SWLatLon.Lng));
ShowMessage('NELat='+FloatToStr(NELatLon.Lat)+' | NELon='+FloatToStr(NELatLon.Lng));
SWLatLon.Free;
NELatLon.Free;
CLatLon.Free;
end;
finally
Free;
end;
For big zoom factors (>=8), where non-uniformity of map scale on y axis can be neglected, there is much easier method, where we just takes into accout 1/cos(latitude) correction for pixels/(degrees of latitude) resolution. Initial resolution for zoom=0 is 256 pixels per 360 degrees both for x and y at 0 lattitude.
def get_static_map_bounds(lat, lng, zoom, sx, sy):
# lat, lng - center
# sx, sy - map size in pixels
# 256 pixels - initial map size for zoom factor 0
sz = 256 * 2 ** zoom
#resolution in degrees per pixel
res_lat = cos(lat * pi / 180.) * 360. / sz
res_lng = 360./sz
d_lat = res_lat * sy / 2
d_lng = res_lng * sx / 2
return ((lat-d_lat, lng-d_lng), (lat+d_lat, lng+d_lng))
I'm a bit new to canvas and such so forgive if it's a trivial question.
I'd like to be able to animate an object following a path (defined as bezier path) but I'm not sure how to do it.
I've looked at Raphael but I can't work out how to follow the path over time.
Cake JS looked promising in the demo, but I'm really struggling the documentation, or lack thereof in this case.
Has anyone got some working example of this?
Use the code on my website from this related question, but instead of changing the .style.left and such in the callback, erase and re-draw your canvas with the item at the new location (and optionally rotation).
Note that this uses SVG internally to easily interpolate points along a bézier curve, but you can use the points it gives you for whatever you want (including drawing on a Canvas).
In case my site is down, here's a current snapshot of the library:
function CurveAnimator(from,to,c1,c2){
this.path = document.createElementNS('http://www.w3.org/2000/svg','path');
if (!c1) c1 = from;
if (!c2) c2 = to;
this.path.setAttribute('d','M'+from.join(',')+'C'+c1.join(',')+' '+c2.join(',')+' '+to.join(','));
this.updatePath();
CurveAnimator.lastCreated = this;
}
CurveAnimator.prototype.animate = function(duration,callback,delay){
var curveAnim = this;
// TODO: Use requestAnimationFrame if a delay isn't passed
if (!delay) delay = 1/40;
clearInterval(curveAnim.animTimer);
var startTime = new Date;
curveAnim.animTimer = setInterval(function(){
var now = new Date;
var elapsed = (now-startTime)/1000;
var percent = elapsed/duration;
if (percent>=1){
percent = 1;
clearInterval(curveAnim.animTimer);
}
var p1 = curveAnim.pointAt(percent-0.01),
p2 = curveAnim.pointAt(percent+0.01);
callback(curveAnim.pointAt(percent),Math.atan2(p2.y-p1.y,p2.x-p1.x)*180/Math.PI);
},delay*1000);
};
CurveAnimator.prototype.stop = function(){
clearInterval(this.animTimer);
};
CurveAnimator.prototype.pointAt = function(percent){
return this.path.getPointAtLength(this.len*percent);
};
CurveAnimator.prototype.updatePath = function(){
this.len = this.path.getTotalLength();
};
CurveAnimator.prototype.setStart = function(x,y){
var M = this.path.pathSegList.getItem(0);
M.x = x; M.y = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setEnd = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x = x; C.y = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setStartDirection = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x1 = x; C.y1 = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setEndDirection = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x2 = x; C.y2 = y;
this.updatePath();
return this;
};
…and here's how you might use it:
var ctx = document.querySelector('canvas').getContext('2d');
ctx.fillStyle = 'red';
var curve = new CurveAnimator([50, 300], [350, 300], [445, 39], [1, 106]);
curve.animate(5, function(point, angle) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillRect(point.x-10, point.y-10, 20, 20);
});
In action: http://jsfiddle.net/Z2YSt/
So, here is the verbose version:
t being any number between 0 and 1 representing time; the p0, p1, p2, p3 objects are the start point, the 1st control point, the 2nd control point an the end point respectively:
var at = 1 - t;
var green1x = p0.x * t + p1.x * at;
var green1y = p0.y * t + p1.y * at;
var green2x = p1.x * t + p2.x * at;
var green2y = p1.y * t + p2.y * at;
var green3x = p2.x * t + p3.x * at;
var green3y = p2.y * t + p3.y * at;
var blue1x = green1x * t + green2x * at;
var blue1y = green1y * t + green2y * at;
var blue2x = green2x * t + green3x * at;
var blue2y = green2y * t + green3y * at;
var finalx = blue1x * t + blue2x * at;
var finaly = blue1y * t + blue2y * at;
Here is a ball using <canvas> following a path in JSfiddle
The names of the variables come from this gif wich is the best explication for bezier curves: http://en.wikipedia.org/wiki/File:Bezier_3_big.gif
A short version of the code, inside a function ready to copy/paste:
var calcBezierPoint = function (t, p0, p1, p2, p3) {
var data = [p0, p1, p2, p3];
var at = 1 - t;
for (var i = 1; i < data.length; i++) {
for (var k = 0; k < data.length - i; k++) {
data[k] = {
x: data[k].x * at + data[k + 1].x * t,
y: data[k].y * at + data[k + 1].y * t
};
}
}
return data[0];
};
Related stuff:
http://blogs.sitepointstatic.com/examples/tech/canvas-curves/bezier-curve.html
http://13thparallel.com/archive/bezier-curves/
http://gsgd.co.uk/sandbox/jquery/easing/jquery.easing.1.3.js
http://www.youtube.com/watch?v=hUCT4b4wa-8
I wouldn't use Canvas for this unless you really have to. SVG has animation along a path built in. Canvas requires quite a bit of math to get it working.
Here's one example of SVG animating along a path.
Here's some discussion about it for raphael: SVG animation along path with Raphael
Please note that Raphael uses SVG and not HTML5 Canvas.
One way to animate along a bezier path in Canvas is to continuously bisect the bezier curve, recoring the midpoints until you have a lot of points (say, 50 points per curve) that you can animate the object along that list of points. Search for bisecting beziers and similar queries for the related math on that.