Convert Lat/Longs to X/Y Co-ordinates - php

I have the Lat/Long value of New York City, NY; 40.7560540,-73.9869510 and a flat image of the earth, 1000px × 446px.
I would like to be able to convert, using Javascript, the Lat/Long to an X,Y coordinate where the point would reflect the location.
So the X,Y coordinate form the Top-Left corner of the image would be; 289, 111
Things to note:
don't worry about issues of what projection to use, make your own
assumption or go with what you know
might work
X,Y can be form any corner of the image
Bonus points for
the same solution in PHP (but I
really need the JS)

The projection you use is going to change everything, but this will work assuming a Mercator projection:
<html>
<head>
<script language="Javascript">
var dot_size = 3;
var longitude_shift = 55; // number of pixels your map's prime meridian is off-center.
var x_pos = 54;
var y_pos = 19;
var map_width = 430;
var map_height = 332;
var half_dot = Math.floor(dot_size / 2);
function draw_point(x, y) {
dot = '<div style="position:absolute;width:' + dot_size + 'px;height:' + dot_size + 'px;top:' + y + 'px;left:' + x + 'px;background:#00ff00"></div>';
document.body.innerHTML += dot;
}
function plot_point(lat, lng) {
// Mercator projection
// longitude: just scale and shift
x = (map_width * (180 + lng) / 360) % map_width + longitude_shift;
// latitude: using the Mercator projection
lat = lat * Math.PI / 180; // convert from degrees to radians
y = Math.log(Math.tan((lat/2) + (Math.PI/4))); // do the Mercator projection (w/ equator of 2pi units)
y = (map_height / 2) - (map_width * y / (2 * Math.PI)) + y_pos; // fit it to our map
x -= x_pos;
y -= y_pos;
draw_point(x - half_dot, y - half_dot);
}
</script>
</head>
<body onload="plot_point(40.756, -73.986)">
<!-- image found at http://www.math.ubc.ca/~israel/m103/mercator.png -->
<img src="mercator.png" style="position:absolute;top:0px;left:0px">
</body>
</html>

A basic conversion function in js would be:
MAP_WIDTH = 1000;
MAP_HEIGHT = 446;
function convert(lat, lon){
var y = ((-1 * lat) + 90) * (MAP_HEIGHT / 180);
var x = (lon + 180) * (MAP_WIDTH / 360);
return {x:x,y:y};
}
This will return the number of pixels from upper left.
This function assumes the following:
That your image is properly aligned
with the upper left corner (0,0)
aligning with 90* North by 180*
West.
That your coords are signed with N being -, S being +, W being - and E being +

If you have a picture of the whole earth, the projection does always matter. But maybe I just don't understand your question.

Related

Get Random XYZ on surface of Sphere from Sphere Loc and Radius

I am trying to get random X Y Z coordinates on the surface of a sphere, based on the spheres center in X Y Z, and it's radius in PHP. Currently, my system is just purely random, hoping for a position on the planets center. I'm inept in math so please bear with me (dyscalculia and dyslexia).
Right now I have just random chaos as follows (note randint is a custom function that checks for rand_int, mt_rand and rand and uses the newest.
$nx = randint( abs( $poo['x'] - $max_distance_x ), abs( $poo['x'] + $max_distance_x ) );
$ny = randint( abs( $poo['y'] - $max_distance_y ), abs( $poo['y'] + $max_distance_y ) );
$nz = randint( abs( $poo['z'] - $max_distance_z ), abs( $poo['z'] + $max_distance_z ) );
but I have the planet radius $pradius, and planet XYZ center array $poo poo(x, y, z), and I think I can get random surface X Y Z, just not sure. I've looked at other languages but am having trouble understanding anything to port.
Update
Based on the two methods provided in an answer I have come up with the following two functions. However both to not function correctly.
The following function only produces craters (points) on the top of the planetoid (north pole), even though it should be calculating from planetoid center at it's radius.
function basic_spheroid_point( $cx, $cy, $cz, $r ) {
$x = randint(-$r, $r);
$y = randint(-$r, $r);
$z = randint(-$r, $r);
$dx = $x - $cx;
$dy = $y - $cy;
$dz = $z - $cz;
$dd = sqrt( ( $dx * $dx ) + ( $dy * $dy ) + ( $dz * $dz ) );
if ( $dd > $r )
return basic_spheroid_point( $cx, $cy, $cz, $r );
$dx = $r * $dx / $dd;
$dy = $r * $dy / $dd;
$dz = $r * $dz / $dd;
return array( $cx + $dx, $cy + $dy, $cz + $dz );
}
This function bunches craters at the North Pole of the planet, though there is also global coverage craters so it is working partially.
function uniform_spheroid_point($cx, $cy, $cz, $r) {
$ap = randint( 0, 359 );
$aq = randint( 0, 359 );
$dx = $r* cos( $ap ) * cos( $aq );
$dy = $r * sin( $aq );
$dz = $r * sin( $ap ) * cos( $aq );
return array( $cx + $dx, $cy + $dy, $cz + $dz );
}
You're currently selecting a point at random from the interior of a parallelepiped with side lengths 2*max_distance_x, 2*max_distance_y and 2*max_distance_z. Let us assume that these are all the same and equivalent to the radius r of the sphere on whose surface you'd like to randomly select points. Then, your method would select randomly from a cube of side length 2r. Essentially none of your random points will actually be on the surface of the sphere of radius r when selected in this way.
However - given a point in the cube, what you can do is this:
calculate the vector from the center point to your random point (the "delta"). For example, if your center point is 100,100,100 and your radius is 100 and you randomly pick point 50,100,150, the vector you're looking for is -50,0,50.
calculate the length of the vector from step 1. This uses the formula for the distance between two points, extended so that it contemplates the point's three coordinates: sqrt(dx^2 + dy^2 +dz^2). For example, our distance is sqrt((-50)^2 + 0^2 + 50^2) = sqrt(2500 + 0 + 2500) = 50sqrt(2).
scale the vector from step 1 by a factor equal to r/d where d is the vector's length as determined in step 2. For our example, we will scale the vector by a factor of r/d = 100/(50sqrt(2)) = 2/sqrt(2) = sqrt(2). This gives -50sqrt(2), 0, 50sqrt(2).
Now, add the scaled vector from step 3 to the center point to get a point on the surface of the sphere of radius r. In our example, the point on the surface of the sphere is 100-50sqrt(2), 100, 100+50sqrt(2).
Now the only issue with this is that some points are more likely to be chosen than others. The reason for this is that some points on the surface of the sphere have more of the cube outside them than other points do. Specifically, a point of the sphere lying on the bounding cube has no points further outside it, but the point on the sphere that intersects a line connecting the center of the cube and one of its corners has a lot of space outside the sphere). To get a truly uniform distribution of points, you'd need to exclude any points selected at random which do not lie inside or on the surface of the sphere. If you do that, you will get a uniform distribution of points by the above method. Because the volume of the cube is 8r^3 and the volume of the sphere is 4/3pir^3, and because 4/3pi ~ 4, you have about a 50% chance at each draw of getting a point that you have to throw away. On average, you'd expect to get one good point in every two draws. You should not typically need many random draws to get a good one, but it is technically unbounded.
If you want to make sure every random draw is a good one, I might suggest selecting two angles uniformly at random from 0 to 360 degrees. Then, use those angles to identify a point on the sphere. So, for instance, suppose you first draw angle p and then angle q. Angle p can determine the plain from which the point will be taken. This plane will intersect the sphere in a circular cross section. Angle q can then determine which point on this intersected circle is returned as the random point. Suppose these angles give point (x', y', z'). Well...
y' = r*sin(q) … since nothing else determines the y coordinate except q
x' = r*cos(p)*cos(q)
z' = r*sin(p)*cos(q)
This has the advantage of not needing to reject any random samples, and the disadvantage of requiring relatively more costly trigonometric operations.
EDIT: Pseudocode for each method
Method 1:
RandomPointOnSphere(centerX, centerY, centerZ, radius)
1. x = random(-radius, radius)
2. y = random(-radius, radius)
3. z = random(-radius, radius)
4. dx = x - centerX
5. dy = y - centerY
6. dz = z - centerZ
7. dd = sqrt(dx*dx + dy*dy + dz*dz)
8. if dd > radius then return RandomPointOnSphere(centerX, centerY, centerZ, radius)
9. dx = radius * dx / dd
10. dy = radius * dy / dd
11. dz = radius * dz / dd
12. return (centerX + dx, centerY + dy, centerZ + dz)
Method 2:
RandomPointOnSphere(centerX, centerY, centerZ, radius)
1. angleP = random(0, 359)
2. angleQ = random(0, 359)
3. dx = radius* cos(angleP) * cos(angleQ)
4. dy = radius * sin(angleQ)
5. dz = radius * sin(angleP) * cos(angleQ)
6. return (centerX + dx, centerY + dy, centerZ + dz)

Reflection Inside Circle Over Time

This is for a game that I am considering.
I have a point that is noted as moving inside a (2D) circle from an arbitrary point, at an arbitrary direction and at a particular time. The point will bounce off the interior wall of the circle when it intersects.
For this example let's say the circle has a diameter of 100 kilometers with it's center at (0,0) and 10 hours ago the point was at location (20,30) with a heading of 40 degrees at a speed of 50kph .
What is the best way to determine where that point currently is and at what direction it's traveling?
I will be implementing this in PHP with the point and circle data stored in MySQL. Since it is a web page there will be no constantly running host process to keep things up to date and the data will need to be refreshed upon a page load.
I'm certainly not looking for anyone to write the code for me but am hoping someone can help me with a somewhat efficient way to approach this.
Your point-object will travel along what is called chords in geometry.
As the object hits the circle boundary, it will reflect from the circle's tangent at that point, and go along a next chord that has the same length. The next hit will be at the same angle (with the tangent at that hit point) as the previous hit, and so it will continue. At a constant speed, the time between hits will be a constant time.
Given the start time and the current time, one can calculate the number of chords that have been completed, and how much of the current chord has been completed. Calculating the position from that is easy when you know the previous and next hit positions. As these hit positions are at equal distances along the circle boundary, that is a matter of converting polar coordinates (an angle, and a distance of 1 radian) to Cartesian coordinates.
I will demonstrate this with JavaScript code. It will not be a great effort to wrap this in PHP:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var radius = canvas.height / 2 - 5;
var pixel = 1/radius;
// transform the canvas so that 0,0 is in the center of the canvas,
// and a unit circle would cover most of the height of the canvas:
context.setTransform(radius, 0, 0, radius, radius+2, radius+2);
// draw unit circle
context.beginPath();
context.arc(0, 0, 1, 0, 2 * Math.PI, false);
context.lineWidth = pixel;
context.strokeStyle = 'black';
context.stroke();
function drawPoint(point) {
// use a different color every 30 seconds:
context.fillStyle = Date.now() % 60000 > 30000 ? 'red' : 'blue';
context.fillRect(point.x-2*pixel, point.y-2*pixel, 4*pixel, 4*pixel);
}
function polarToCartesian(rad, dist) {
return {
x: Math.cos(rad) * dist,
y: Math.sin(rad) * dist
}
}
function pointBetween(a, b, fractionTravelled) {
return {
x: a.x + (b.x-a.x)*fractionTravelled,
y: a.y + (b.y-a.y)*fractionTravelled
}
}
// 4 parameters are needed:
var startRadians = 0; // distance along circle boundary from (0,1)
var hitAngle = Math.PI/2.931; // PI/2 would be head-on impact along diagonal
var speed = 0.4; // radians per second
var startTime = Date.now()/1000; // seconds
//
// Calculate some derived values which remain constant:
// - theta as used on https://en.wikipedia.org/wiki/Chord_%28geometry%29
// - chordSize formula comes from that wiki article.
var theta = 2 * hitAngle;
var chordSize = 2 * Math.sin(theta/2); // in radians
function drawCurrentPosition() {
// Note that this calculation does not look at the previous result,
// but uses the original parameters and time passed to calculate
// the objects current position.
var elapsedTime = Date.now()/1000 - startTime; // in secs
var distanceTravelled = speed * elapsedTime; // in radians
var chordsTravelled = distanceTravelled / chordSize; // in number of chords
var chordsTravelledComplete = Math.floor(chordsTravelled);
var fractionOnChord = chordsTravelled - chordsTravelledComplete; // 0<=f<1
var lastHitRadians = startRadians + chordsTravelledComplete * theta; // rad
var nextHitRadians = lastHitRadians + theta;
var lastHitPos = polarToCartesian(lastHitRadians, 1); // (x,y)
var nextHitPos = polarToCartesian(nextHitRadians, 1);
var currentPos = pointBetween(lastHitPos, nextHitPos, fractionOnChord);
drawPoint(currentPos);
}
// Demo: keep drawing the object's position every 0.1 second:
setInterval(drawCurrentPosition, 100);
<canvas id="myCanvas" width="200" height="200"></canvas>
Addendum: PHP code
Here is some code that could be useful for use in PHP. It uses the same calculations as the above JavaScript code, but does not keep running. Instead it first checks if there is a started game in the session scope, if not, it starts the "clock". At every request (reload of the page), the new position is calculated and printed on the page as an X,Y pair.
The coordinates are normalised, based on a unit circle (radius 1). The game parameters are hard-coded, but you could easily let them be passed via POST/GET parameters:
session_start(); // needed to persist game data for this user session
function getNewGame($startRadians, $hitAngle, $speed) {
$game = array();
$game["startTime"] = microtime(true);
$game["startRadians"] = $startRadians;
$game["theta"] = 2 * $hitAngle;
$game["chordSize"] = 2 * sin($hitAngle);
$game["speed"] = $speed;
return (object) $game;
}
function polarToCartesian($rad, $dist) {
return (object) array(
"x" => cos($rad) * $dist,
"y" => sin($rad) * $dist
);
}
function pointBetween($a, $b, $fractionTravelled) {
return (object) array(
"x" => $a->x + ($b->x-$a->x)*$fractionTravelled,
"y" => $a->y + ($b->y-$a->y)*$fractionTravelled
);
}
function getCurrentPosition($game) {
// Note that this calculation does not look at the previous result,
// but uses the original parameters and time passed to calculate
// the objects current position.
$elapsedTime = microtime(true) - $game->startTime; // in secs
$distanceTravelled = $game->speed * $elapsedTime; // in radians
$chordsTravelled = $distanceTravelled / $game->chordSize; //number of chords
$chordsTravelledComplete = floor($chordsTravelled);
$fractionOnChord = $chordsTravelled - $chordsTravelledComplete; // 0<=f<1
$lastHitRadians = $game->startRadians
+ $chordsTravelledComplete * $game->theta; // in radians on circle
$nextHitRadians = $lastHitRadians + $game->theta;
$lastHitPos = polarToCartesian($lastHitRadians, 1); // (x,y)
$nextHitPos = polarToCartesian($nextHitRadians, 1);
$currentPos = pointBetween($lastHitPos, $nextHitPos, $fractionOnChord);
return $currentPos;
}
// check if this is the first time the user loads this page:
if (!isset($_SESSION["game"])) {
// start game with some game parameters:
$_SESSION["game"] = getNewGame(0, pi()/2.931, 0.4);
}
// calculate the position based on game info and current time:
$pos = getCurrentPosition($_SESSION["game"]);
// print the result:
echo "Current position: {$pos->x}, {$pos->y}<br>";

Calculate the center of latitude and longitude coordinates

I'm looking for a elegant solution that calculates the center between several latitude-longitude coordinates (for example, to simply center a map to the center of a google-maps polygon).
Table: locations:
id | city | latitude | longitude
-----------------------------------------------
1 | Berlin | 52.524268 | 13.406290
-----------------------------------------------
2 | London | 51.508129 | -0.1280050
-----------------------------------------------
3 | Hamburg | 53.551084 | 9.9936817
-----------------------------------------------
4 | Amsterdam | 52.370215 | 4.8951678
-----------------------------------------------
The current calculation:
function calculateCenter($array_locations) {
$minlat = false;
$minlng = false;
$maxlat = false;
$maxlng = false;
foreach ($array_locations as $geolocation) {
if ($minlat === false) { $minlat = $geolocation['lat']; } else { $minlat = ($geolocation['lat'] < $minlat) ? $geolocation['lat'] : $minlat; }
if ($maxlat === false) { $maxlat = $geolocation['lat']; } else { $maxlat = ($geolocation['lat'] > $maxlat) ? $geolocation['lat'] : $maxlat; }
if ($minlng === false) { $minlng = $geolocation['lon']; } else { $minlng = ($geolocation['lon'] < $minlng) ? $geolocation['lon'] : $minlng; }
if ($maxlng === false) { $maxlng = $geolocation['lon']; } else { $maxlng = ($geolocation['lon'] > $maxlng) ? $geolocation['lon'] : $maxlng; }
}
// Calculate the center
$lat = $maxlat - (($maxlat - $minlat) / 2);
$lon = $maxlng - (($maxlng - $minlng) / 2);
return array($lat, $lon);
}
As you are using Google Maps you can use getBounds() method and getCenter() method.
I have rearranged your coordinates to form a Convex Polygon (All the vertices point 'outwards', away from the center).The polygon is closed by having the first coordinate as the first and last value in polygonCoords array.
See jsfiddle
var map;
var polygon;
var bounds = new google.maps.LatLngBounds();
var i;
var myLatLng = new google.maps.LatLng(52.5,6.6);
var myOptions = {
zoom: 5,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.TERRAIN
};
map = new google.maps.Map(document.getElementById("map_canvas"),
myOptions);
var polygonCoords = [
new google.maps.LatLng(52.524268,13.406290),
new google.maps.LatLng(53.551084,9.9936817),
new google.maps.LatLng(51.508129,-0.1280050),
new google.maps.LatLng(52.370215,4.8951678),
new google.maps.LatLng(52.524268,13.406290)//Start & end point
];
polygon = new google.maps.Polygon({
paths: polygonCoords,
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 3,
fillColor: "#FF0000",
fillOpacity: 0.05
});
polygon.setMap(map);
for (i = 0; i < polygonCoords.length; i++) {
bounds.extend(polygonCoords[i]);
}
// The Center of the polygon
var latlng = bounds.getCenter();
var marker = new google.maps.Marker({
position: latlng,
map: map,
title:latlng.toString()
});
Averaging your latitudes and longitudes works in many cases, but have problems in a number of cases. Example, you have 2 cites, Tokyo (long = 140) and Seattle (long -122), your average longitude is 18, somewhere in Europe. You would expect something closer to the international date line, 180 degrees away.
The most direct, no problem method, is to average the vectors as if each originated from the earth's center.
Pseudo code, (assumes radians)
for each lat,long
// assume 1 radii from the earth's center.
// covert lat, long, and radii into x,y,z (spherical to cartesian coordinates)
r=1, theta=pi/2 - lat, phi=long
x = r*sin(theta)*cos(phi)
y = r*sin(theta)*sin(phi)
z = r*cos(theta)
N++;
// accumulate x,y,z
sum_x += x, etc.
// average x,y,z
avg_x = sum_x/N, etc.
// convert x,y,z back to spherical co-ordinates to get the lat/long center.
rho = sqrt(avg_x*avg_x + avg_y*avg_y + avg_z*avg_z)
lat = pi/2 - acos(avg_z/rho) // acos() results are 0 to pi
long = atan2(avg_y, avg_x) // 4 quadrant arctangent
[Edit Corrected spherical co-ordinates to cartesian]
Google uses a Mercator projection, treating the earth as an elongated cylinder. Thus the problem to to find the center of that projection.
For each lat/long pair, convert to map scaled x,y co-ordinates (using radians):
x = long
y = ln(tan(pi/4 + lat/2)) // Mercator projection
Then, for x & y, find the average of the minimum and maximum to get your center. Convert back to lat/long as follows
Pseudo code
center_long = average(minimum_x, maximum_x)
center_lat = (atan(exp(average(minimum_y, maximum_y))) - pi/4)*2
The calculation of the center longitude works fine were it not for the circular nature of the cylindric Earth projection. If the longitudes are in both the Eastern and Western hemispheres (some negative, some positive), than additional work may be needed.
Pseudo code
sort the longitudes into ascending order
for each longitude
difference = longitude(i-1) - longitude(i)
// for first, use longitude(0) - longitude(last)
if (difference < 0) add 2*pi (360 degrees)
Keep track of index of minimal difference
The pair with the minimal difference represents the pair that most tightly contains all longitudes.
Average this pair for the center longitude.
If this pair was index 0 & last, add pi (180 degrees)
OP 4 city result: (52.4 N, 7.0 E)
This is my second answer, for the first does not get the the crux of OP's post. Since it has some value it remains.

Finding Points in a Rectangle or Circle with mysql

I have a mysql database table with a list points with their Co-ordinates (x,y)
I want to find the list of points which fall inside the rectangle. This would have been simple had any one side of the rectangle been aligned parallel or perpendicular to any axis. But is not. Which means the rectangle is rotated.
I also have to find the points inside a circle.
Known Data for Rectangle
-Coordinates for all the four points
Known Data for Circle
-Co-ordinates for the center and the radius.
How do I query the mysql table to find the points falling in the rectangle and the circle?
If it matters then the front end I am using is PHP.
A rectangle can be defined by two points representing the opposing corners, eg: A(x,y) and B(x,y). If you have a point C(x,y) that you want to test to see if it is inside the rectangle then:
IF( (Cx BETWEEN Ax AND Bx) AND (Cy BETWEEN Ay AND By) ) THEN
point C is in the rectangle defined by points A and B
ELSE
nope
ENDIF
A circle can be defined by a single point C(x,y) and a radius R. If the distance D between the center and the point P(x,y) is less than the radius R, then it is inside the circle:
And of course you remember the Pythagorean Theoreom, right?
C² = A² + B² SO C = SQRT(A² + B²)
So:
D = SQRT( ABS(Cx - Px)² + ABS(Cy - Py)²)
IF( D <= R ) THEN
point P is inside the circle with center C and radius R
ELSE
nope
ENDIF
edit:
The algorithm for checking if a point is within a polygon is a bit more complex than what I'd prefer to write in a SQL query or stored procedure, but it is entirely possible. It's worth noting that it runs in constant-time and is very lightweight. [requires roughly 6 arithmetic ops and maybe 2 or 3 logic ops for each point in the poly]
To pare down the number calculations required you can simply write your select to get points within a rough bounding box before procesing them further:
WHERE
x BETWEEN MIN(x1,x2,x3,x4) AND MAX(x1,x2,x3,x4)
AND
y BETWEEN MIN(y1,y2,y3,y4) AND MAX(y1,y2,y3,y4)
Assuming the columns containing the x and y values are indexed this might use a few less CPU cycles than simply doing the math, but it's debatable and I'm inclined to call it a wash.
As for the circle you can't possibly get more efficient than
WHERE
SQRT( POW(ABS($Cx - x),2) + POW(ABS($Cy - y),2) ) < $radius
You're far too concerned with the perceived cost of these calculations, just write the code and get it working. This is not the stage to be performing such niggling optimizations.
One thing to add to #Sammitch's answer is, calculating haversine distance in case you are looking at latitudes and longitudes on world map (distance calculation on a spherical surface, since Earth is a sphere ;) https://en.wikipedia.org/wiki/Haversine_formula)
Here's a vanilla Javascript example for calculating that:
function calculateHaversineDistance(lat1x, lon1, lat2x, lon2) {
var R = 6371; // km
var dLat = toRad(lat2x-lat1x);
var dLon = toRad(lon2-lon1);
var lat1 = toRad(lat1x);
var lat2 = toRad(lat2x);
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
function toRad(x) {
return x * Math.PI / 180;
}
EDIT->
Here's a php version I wrote:
function toRad($x) {
return $x * pi() / 180;
}
function calculateHaversineDistance($lat1, $lon1, $lat2, $lon2) {
$R = 6371; // km
$dLat = $this->toRad($lat2-$lat1);
$dLon = $this->toRad($lon2-$lon1);
$lat1 = $this->toRad($lat1);
$lat2 = $this->toRad($lat2);
$a = sin($dLat/2) * sin($dLat/2) +
sin($dLon/2) * sin($dLon/2) * cos($lat1) * cos($lat2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
return $R * $c;
}

removing / smoothing rough edges in gd library

I m using GD library to create images on the fly.
But when i rotate image using imagerotate() function
it works fine but it gives very much irritating rough edges of image
which is rotated.
as it is shown in this picture.
So how to make these sides/edges of rotated image smooth ?
One way to avoid from getting the Jaggies effect when rotating images is by using another way to sample the pixels than just taking the adjusted pixels, for example to use Nearest-neighbor interpolation to make the edges smoother. You can see matlab code example:
im1 = imread('lena.jpg');imshow(im1);
[m,n,p]=size(im1);
thet = rand(1);
mm = m*sqrt(2);
nn = n*sqrt(2);
for t=1:mm
for s=1:nn
i = uint16((t-mm/2)*cos(thet)+(s-nn/2)*sin(thet)+m/2);
j = uint16(-(t-mm/2)*sin(thet)+(s-nn/2)*cos(thet)+n/2);
if i>0 && j>0 && i<=m && j<=n
im2(t,s,:)=im1(i,j,:);
end
end
end
figure;
imshow(im2);
taken from (here). Basically it means that when sampling the pixels in the original picture, we sample near pixels and interpolate them to get the target pixel value. This way
you can achive waht you want withourt installing any additiional packages.
EDIT
I've found some old code I once wrote in Java, which contains implementations of a couple of sampling algorithems. Here is the code:
Nearest Neighbor sampler:
/**
* #pre (this!=null) && (this.pixels!=null)
* #post returns the sampled pixel of (x,y) by nearest neighbor sampling
*/
private Pixel sampleNearestNeighbor(double x, double y) {
int X = (int) Math.round(x);
int Y = (int) Math.round(y);
if (X >= 0 && Y >= 0 && X < this.pixels.length
&& Y < this.pixels[0].length)
// (X,Y) is within this.pixels' borders
return new Pixel(pixels[X][Y].getRGB());
else
return new Pixel(255, 255, 255);
// sample color will be default white
}
Bilinear sampler:
/**
* #pre (this!=null) && (this.pixels!=null)
* #post returns the sampled pixel of (x,y) by bilinear interpolation
*/
private Pixel sampleBilinear(double x, double y) {
int x1, y1, x2, y2;
x1 = (int) Math.floor(x);
y1 = (int) Math.floor(y);
double weightX = x - x1;
double weightY = y - y1;
if (x1 >= 0 && y1 >= 0 && x1 + 1 < this.pixels.length
&& y1 + 1 < this.pixels[0].length) {
x2 = x1 + 1;
y2 = y1 + 1;
double redAX = (weightX * this.pixels[x2][y1].getRed())
+ (1 - weightX) * this.pixels[x1][y1].getRed();
double greenAX = (weightX * this.pixels[x2][y1].getGreen())
+ (1 - weightX) * this.pixels[x1][y1].getGreen();
double blueAX = (weightX * this.pixels[x2][y1].getBlue())
+ (1 - weightX) * this.pixels[x1][y1].getBlue();
// bilinear interpolation of A point
double redBX = (weightX * this.pixels[x2][y2].getRed())
+ (1 - weightX) * this.pixels[x1][y2].getRed();
double greenBX = (weightX * this.pixels[x2][y2].getGreen())
+ (1 - weightX) * this.pixels[x1][y2].getGreen();
double blueBX = (weightX * this.pixels[x2][y2].getBlue())
+ (1 - weightX) * this.pixels[x1][y2].getBlue();
// bilinear interpolation of B point
int red = (int) (weightY * redBX + (1 - weightY) * redAX);
int green = (int) (weightY * greenBX + (1 - weightY) * greenAX);
int blue = (int) (weightY * blueBX + (1 - weightY) * blueAX);
// bilinear interpolation of A and B
return new Pixel(red, green, blue);
} else if (x1 >= 0
&& y1 >= 0 // last row or column
&& (x1 == this.pixels.length - 1 || y1 == this.pixels[0].length - 1)) {
return new Pixel(this.pixels[x1][y1].getRed(), this.pixels[x1][y1]
.getGreen(), this.pixels[x1][y1].getBlue());
} else
return new Pixel(255, 255, 255);
// sample color will be default white
}
Gaussian sampler:
/**
* #pre (this!=null) && (this.pixels!=null)
* #post returns the sampled pixel of (x,y) by gaussian function
*/
private Pixel sampleGaussian(double u, double v) {
double w = 3; // sampling distance
double sqrSigma = Math.pow(w / 3.0, 2); // sigma^2
double normal = 0;
double red = 0, green = 0, blue = 0;
double minIX = Math.round(u - w);
double maxIX = Math.round(u + w);
double minIY = Math.round(v - w);
double maxIY = Math.round(v + w);
for (int ix = (int) minIX; ix <= maxIX; ix++) {
for (int iy = (int) minIY; iy <= maxIY; iy++) {
double sqrD = Math.pow(ix - u, 2) + Math.pow(iy - v, 2);
// squared distance between (ix,iy) and (u,v)
if (sqrD < Math.pow(w, 2) && ix >= 0 && iy >= 0
&& ix < pixels.length && iy < pixels[0].length) {
// gaussian function
double gaussianWeight = Math.pow(2, -1 * (sqrD / sqrSigma));
normal += gaussianWeight;
red += gaussianWeight * pixels[ix][iy].getRed();
green += gaussianWeight * pixels[ix][iy].getGreen();
blue += gaussianWeight * pixels[ix][iy].getBlue();
}
}
}
red /= normal;
green /= normal;
blue /= normal;
return new Pixel(red, green, blue);
}
Actual rotate:
/**
* #pre (this!=null) && (this.pixels!=null) && (1 <= samplingMethod <= 3)
* #post creates a new rotated-by-degrees Image and returns it
*/
public myImage rotate(double degrees, int samplingMethod) {
myImage outputImg = null;
int t = 0;
for (; degrees < 0 || degrees >= 180; degrees += (degrees < 0) ? 180
: -180)
t++;
int w = this.pixels.length;
int h = this.pixels[0].length;
double cosinus = Math.cos(Math.toRadians(degrees));
double sinus = Math.sin(Math.toRadians(degrees));
int width = Math.round((float) (w * Math.abs(cosinus) + h * sinus));
int height = Math.round((float) (h * Math.abs(cosinus) + w * sinus));
w--;
h--; // move from (1,..,k) to (0,..,1-k)
Pixel[][] pixelsArray = new Pixel[width][height];
double x = 0; // x coordinate in the source image
double y = 0; // y coordinate in the source image
if (degrees >= 90) { // // 270 or 90 degrees turn
double temp = cosinus;
cosinus = sinus;
sinus = -temp;
}
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
double x0 = i;
double y0 = j;
if (degrees >= 90) {
if ((t % 2 == 1)) { // 270 degrees turn
x0 = j;
y0 = width - i - 1;
} else { // 90 degrees turn
x0 = height - j - 1;
y0 = i;
}
} else if (t % 2 == 1) { // 180 degrees turn
x0 = width - x0 - 1;
y0 = height - y0 - 1;
}
// calculate new x/y coordinates and
// adjust their locations to the middle of the picture
x = x0 * cosinus - (y0 - sinus * w) * sinus;
y = x0 * sinus + (y0 - sinus * w) * cosinus;
if (x < -0.5 || x > w + 0.5 || y < -0.5 || y > h + 0.5)
// the pixels that does not have a source will be painted in
// default white
pixelsArray[i][j] = new Pixel(255, 255, 255);
else {
if (samplingMethod == 1)
pixelsArray[i][j] = sampleNearestNeighbor(x, y);
else if (samplingMethod == 2)
pixelsArray[i][j] = sampleBilinear(x, y);
else if (samplingMethod == 3)
pixelsArray[i][j] = sampleGaussian(x, y);
}
}
outputImg = new myImage(pixelsArray);
}
return outputImg;
}
It may sound rather hack-ish, but it is the simplest way to do it, and even big enterprise solutions use this.
The trick is to first create the image 2X the size of what you need, then do all the drawing calls and then resize it to required original size.
Not only it is really easy to do, but also it is as fast as it gets and it produces very nice results. I use this trick for all cases when I need to apply blur to edges.
Another advantate to this is that it does not include blur on the rest of the image and it remains crisp clear - only borders of rotated image gets smoothed.
One thing you could try is to use imageantialias() to smooth the edges.
If that doesn't suit your needs, GD itself will probably not suffice.
GD uses very fast methods for all it's abilites without any actual smoothing or anything like that involved. If you want some proper image editing, you could either look into ImageMagick (which requires extra-software on the server) or write your own functions based on GD.
Keep in mind though, that php is really slow with huge amounts of data, so writing your own functions might be disappointing. (From my experience, PHP is roughly 40 times slower than compiled code.)
I recommend using ImageMagick for any image work where result quality matters.

Categories