Spherical Coordinates within a range / restriction - actionscript

The function below returns points on a sphere with a given radius. I want to add restriction such that points cannot be plotted within 30 degrees of the poles of the sphere.
public static function randomPoint(radius:Number):Number3D
{
var inclination:Number = Math.random() * Math.PI*2;
var azimuth:Number = Math.random() * Math.PI*2;
var point:Number3D = new Number3D(
radius * Math.sin(inclination) * Math.cos(azimuth),
radius * Math.sin(inclination) * Math.sin(azimuth),
radius * Math.cos(inclination)
);
return point;
}
Thanks in advance!

Sounds like you can just restrict the inclination:
var inclination:Number = (Math.PI/6) + Math.random()*(2*Math.PI-2*Math.PI/6)
Feel free to resolve those constant values, just kept them in to show the working.

Here's what I have so far... this does what I want, restricted north and south poles. Any improvements welcome!
var point:Number3D = sphericalPoint(100, inclination, azimuth);
public static function sphericalPoint(r:Number, inc:Number, az:Number):Number3D
{
var point:Number3D = new Number3D(
r * Math.sin(inc) * Math.cos(az),
r * Math.sin(inc) * Math.sin(az),
r * Math.cos(inc)
);
//cheat and use a transform matrix
var obj:Object3D = new Object3D();
obj.rotationX = 90;
point.rotate(point, obj.transform);
return point;
}
//a number between 0 and 180
protected function get inclination():Number
{
//default
//_inclination = Math.random() * Math.PI;
//number between 60 and 120
_inclination = Math.random() * (Math.PI*5/6 - Math.PI/6) + Math.PI/6;
return _inclination;
}
//a number between 0 and 360
protected function get azimuth():Number
{
//revolve around the Y axis
_azimuth = Math.random() * Math.PI*2;
return _azimuth;
}

Related

I want to calculate distance between two locations in km in ionic 4. I use a function Which give me error [duplicate]

This question already has answers here:
toRad() Javascript function throwing error
(6 answers)
Closed 2 years ago.
distance(lon1, lat1, lon2, lat2) {
var R = 6371; // Radius of the earth in km
var dLat = (lat2-lat1).toRad(); // Javascript functions in radians
var dLon = (lon2-lon1).toRad();
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c; // Distance in km
return d;
}
If I use this function in which I use a toRad() function. whenever I use it it will give an error:
Property toRad() does not exist type number.
Please help me what's the reason is that I have to import something?
You should write your own toRad function like this:
function toRad(Value) {
return Value * Math.PI / 180;
}
An then use it in a functional way:
function distance(lon1, lat1, lon2, lat2) {
var R = 6371;
var dLat = toRad(lat2 - lat1);
var dLon = toRad(lon2 - lon1);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
}
Btw, are you writing your app in Ionic Angular? Shouldn't you be using Typescript rather than JavaScript?

How to get buffer polygon coordinates (latitude and longitude) in iOS?

I have created one polygon on map with some set of coordinates.
I need help regarding making one buffered polygon with some given distance outside of original polygon border.
so what i need a method with such algorithm in which i pass set of coordinates as input and should get buffered set of coordinates as output.
I tried to achieve this by using arcgis library for ios with bufferGeometry method of AGSGeometryEngine but problem is, this is tightly coupled and only will work their GIS Map but I am using Mapbox which is different Map. So I want one generic method which can resolve my problem independent to map.
The solution of #Ravikant Paudel though comprehensive didn't work for me so I have implemented the approach myself.
Also, I implemented the approach in kotlin and adding it here so that someone else who is facing a similar problem will find it helpful.
Approach:
Find the angle of the angle bisector theta for every vertice of the polygon.
Draw a circle with radius = bufferedDistance / sin(angleBisctorTheta)
Find intersections of the circle and angle bisector.
Out of the 2 intersection points the one inside the polygon will give you the buffered vertice for the shrunk polygon and the outside point for the buffered polygon.
This approach does not account for the corner cases in which both points somehow fall inside or outside the polygon -> in which case the buffered polygon formed will be malformed.
Code:
private fun computeAngleBisectorTheta(
prevLatLng: LatLng,
currLatLng: LatLng,
nextLatLng: LatLng
): Double {
var phiBisector = 0.0
try {
val aPrime = getDeltaPrimeVector(prevLatLng, currLatLng)
val cPrime = getDeltaPrimeVector(nextLatLng, currLatLng)
val thetaA = atan2(aPrime[1], aPrime[0])
val thetaC = atan2(cPrime[1], cPrime[0])
phiBisector = (thetaA + thetaC) / 2.0
} catch (e: Exception) {
logger.error("[Error] in computeAngleBisectorSlope: $e")
}
return phiBisector
}
private fun getDeltaPrimeVector(
aLatLng: LatLng,
bLatLng: LatLng
): ArrayList<Double> {
val arrayList: ArrayList<Double> = ArrayList<Double>(2)
try {
val aX = convertToXY(aLatLng.latitude)
val aY = convertToXY(aLatLng.longitude)
val bX = convertToXY(bLatLng.latitude)
val bY = convertToXY(bLatLng.longitude)
arrayList.add((aX - bX))
arrayList.add((aY - bY))
} catch (e: Exception) {
logger.error("[Error] in getDeltaPrimeVector: $e")
}
return arrayList
}
private fun convertToXY(coordinate: Double) =
EARTH_RADIUS * toRad(coordinate)
private fun convertToLatLngfromXY(coordinate: Double) =
toDegrees(coordinate / EARTH_RADIUS)
private fun computeBufferedVertices(
angle: Double, bufDis: Int,
centerLatLng: LatLng
): ArrayList<LatLng> {
var results = ArrayList<LatLng>()
try {
val distance = bufDis / sin(angle)
var slope = tan(angle)
var inverseSlopeSquare = sqrt(1 + slope * slope * 1.0)
var distanceByInverseSlopeSquare = distance / inverseSlopeSquare
var slopeIntoDistanceByInverseSlopeSquare = slope * distanceByInverseSlopeSquare
var p1X: Double = convertToXY(centerLatLng.latitude) + distanceByInverseSlopeSquare
var p1Y: Double =
convertToXY(centerLatLng.longitude) + slopeIntoDistanceByInverseSlopeSquare
var p2X: Double = convertToXY(centerLatLng.latitude) - distanceByInverseSlopeSquare
var p2Y: Double =
convertToXY(centerLatLng.longitude) - slopeIntoDistanceByInverseSlopeSquare
val tempLatLng1 = LatLng(convertToLatLngfromXY(p1X), convertToLatLngfromXY(p1Y))
results.add(tempLatLng1)
val tempLatLng2 = LatLng(convertToLatLngfromXY(p2X), convertToLatLngfromXY(p2Y))
results.add(tempLatLng2)
} catch (e: Exception) {
logger.error("[Error] in computeBufferedVertices: $e")
}
return results
}
private fun getVerticesOutsidePolygon(
verticesArray: ArrayList<LatLng>,
polygon: ArrayList<LatLng>
): LatLng {
if (isPointInPolygon(
verticesArray[0].latitude,
verticesArray[0].longitude,
polygon
)
) {
if (sPointInPolygon(
verticesArray[1].latitude,
verticesArray[1].longitude,
polygon
)
) {
logger.error("[ERROR] Malformed polygon! Both Vertices are inside the polygon! $verticesArray")
} else {
return verticesArray[1]
}
} else {
if (PolygonGeofenceHelper.isPointInPolygon(
verticesArray[1].latitude,
verticesArray[1].longitude,
polygon
)
) {
return verticesArray[0]
} else {
logger.error("[ERROR] Malformed polygon! Both Vertices are outside the polygon!: $verticesArray")
}
}
//returning a vertice anyway because there is no fall back policy designed if both vertices are inside or outside the polygon
return verticesArray[0]
}
private fun toRad(angle: Double): Double {
return angle * Math.PI / 180
}
private fun toDegrees(radians: Double): Double {
return radians * 180 / Math.PI
}
private fun getVerticesInsidePolygon(
verticesArray: ArrayList<LatLng>,
polygon: ArrayList<LatLng>
): LatLng {
if (isPointInPolygon(
verticesArray[0].latitude,
verticesArray[0].longitude,
polygon
)
) {
if (isPointInPolygon(
verticesArray[1].latitude,
verticesArray[1].longitude,
polygon
)
) {
logger.error("[ERROR] Malformed polygon! Both Vertices are inside the polygon! $verticesArray")
} else {
return verticesArray[0]
}
} else {
if (PolygonGeofenceHelper.isPointInPolygon(
verticesArray[1].latitude,
verticesArray[1].longitude,
polygon
)
) {
return verticesArray[1]
} else {
logger.error("[ERROR] Malformed polygon! Both Vertices are outside the polygon!: $verticesArray")
}
}
//returning a vertice anyway because there is no fall back policy designed if both vertices are inside or outside the polygon
return LatLng(0.0, 0.0)
}
fun getBufferedPolygon(
polygon: ArrayList<LatLng>,
bufferDistance: Int,
isOutside: Boolean
): ArrayList<LatLng> {
var bufferedPolygon = ArrayList<LatLng>()
var isBufferedPolygonMalformed = false
try {
for (i in 0 until polygon.size) {
val prevLatLng: LatLng = polygon[if (i - 1 < 0) polygon.size - 1 else i - 1]
val centerLatLng: LatLng = polygon[i]
val nextLatLng: LatLng = polygon[if (i + 1 == polygon.size) 0 else i + 1]
val computedVertices =
computeBufferedVertices(
computeAngleBisectorTheta(
prevLatLng, centerLatLng, nextLatLng
), bufferDistance, centerLatLng
)
val latLng = if (isOutside) {
getVerticesOutsidePolygon(
computedVertices,
polygon
)
} else {
getVerticesInsidePolygon(
computedVertices,
polygon
)
}
if (latLng.latitude == 0.0 && latLng.longitude == 0.0) {
isBufferedPolygonMalformed = true
break
}
bufferedPolygon.add(latLng)
}
if (isBufferedPolygonMalformed) {
bufferedPolygon = polygon
logger.error("[Error] Polygon generated is malformed returning the same polygon: $polygon , $bufferDistance, $isOutside")
}
} catch (e: Exception) {
logger.error("[Error] in getBufferedPolygon: $e")
}
return bufferedPolygon
}
You'll need to pass an array of points present in the polygon in the code and the buffer distance the third param is to get the outside buffer or the inside buffer. (Note: I am assuming that the vertices in this list are adjacent to each other).
I have tried to keep this answer as comprehensive as possible. Please feel free to suggest any improvements or a better approach.
You can find the detailed math behind the above code on my portfolio page.
Finding angle bisector
To approximate latitude and longitude to a 2D cartesian coordinate system.
To check if the point is inside a polygon I am using the approach mentioned in this geeks for geeks article
I have same problem in my app and finally found the solution by the help of this site
I am an android developer and my code may not be useful to you but the core concept is same.
At first we need to find the bearing of the line with the help of two points LatLng points.(i have done by using computeDistanceAndBearing(double lat1, double lon1,double lat2, double lon2) function)
Now to get the buffering of certain point we need to give the buffering distance ,LatLng point and bearing (which i obtain from computeDistanceAndBearing function).(I have done this by using computeDestinationAndBearing(double lat1, double lon1,double brng, double dist) function ). from single LatLng point we get two points by producing them with their bearing with certain distance.
Now we need to find the interestion point of the two point to get the buffering that we want. for this remember to take new obtain point and bearing of another line and same with another. This helps to obtain new intersection point with buffering you want.(i have done this in my function computeIntersectionPoint(LatLng p1, double brng1, LatLng p2, double brng2))
Do this to all the polygon points and then you get new points whichyou joint to get buffering.
This is the way i have done in my android location app whis is
Here is my code
//computeDistanceAndBearing(double lat1, double lon1,
double lat2, double lon2)
public static double[] computeDistanceAndBearing(double lat1, double lon1,
double lat2, double lon2) {
// Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
// using the "Inverse Formula" (section 4)
double results[] = new double[3];
int MAXITERS = 20;
// Convert lat/long to radians
lat1 *= Math.PI / 180.0;
lat2 *= Math.PI / 180.0;
lon1 *= Math.PI / 180.0;
lon2 *= Math.PI / 180.0;
double a = 6378137.0; // WGS84 major axis
double b = 6356752.3142; // WGS84 semi-major axis
double f = (a - b) / a;
double aSqMinusBSqOverBSq = (a * a - b * b) / (b * b);
double L = lon2 - lon1;
double A = 0.0;
double U1 = Math.atan((1.0 - f) * Math.tan(lat1));
double U2 = Math.atan((1.0 - f) * Math.tan(lat2));
double cosU1 = Math.cos(U1);
double cosU2 = Math.cos(U2);
double sinU1 = Math.sin(U1);
double sinU2 = Math.sin(U2);
double cosU1cosU2 = cosU1 * cosU2;
double sinU1sinU2 = sinU1 * sinU2;
double sigma = 0.0;
double deltaSigma = 0.0;
double cosSqAlpha = 0.0;
double cos2SM = 0.0;
double cosSigma = 0.0;
double sinSigma = 0.0;
double cosLambda = 0.0;
double sinLambda = 0.0;
double lambda = L; // initial guess
for (int iter = 0; iter < MAXITERS; iter++) {
double lambdaOrig = lambda;
cosLambda = Math.cos(lambda);
sinLambda = Math.sin(lambda);
double t1 = cosU2 * sinLambda;
double t2 = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda;
double sinSqSigma = t1 * t1 + t2 * t2; // (14)
sinSigma = Math.sqrt(sinSqSigma);
cosSigma = sinU1sinU2 + cosU1cosU2 * cosLambda; // (15)
sigma = Math.atan2(sinSigma, cosSigma); // (16)
double sinAlpha = (sinSigma == 0) ? 0.0 : cosU1cosU2 * sinLambda
/ sinSigma; // (17)
cosSqAlpha = 1.0 - sinAlpha * sinAlpha;
cos2SM = (cosSqAlpha == 0) ? 0.0 : cosSigma - 2.0 * sinU1sinU2
/ cosSqAlpha; // (18)
double uSquared = cosSqAlpha * aSqMinusBSqOverBSq; // defn
A = 1 + (uSquared / 16384.0) * // (3)
(4096.0 + uSquared * (-768 + uSquared * (320.0 - 175.0 * uSquared)));
double B = (uSquared / 1024.0) * // (4)
(256.0 + uSquared * (-128.0 + uSquared * (74.0 - 47.0 * uSquared)));
double C = (f / 16.0) * cosSqAlpha * (4.0 + f * (4.0 - 3.0 * cosSqAlpha)); // (10)
double cos2SMSq = cos2SM * cos2SM;
deltaSigma = B
* sinSigma
* // (6)
(cos2SM + (B / 4.0)
* (cosSigma * (-1.0 + 2.0 * cos2SMSq) - (B / 6.0) * cos2SM
* (-3.0 + 4.0 * sinSigma * sinSigma)
* (-3.0 + 4.0 * cos2SMSq)));
lambda = L
+ (1.0 - C)
* f
* sinAlpha
* (sigma + C * sinSigma
* (cos2SM + C * cosSigma * (-1.0 + 2.0 * cos2SM * cos2SM))); // (11)
double delta = (lambda - lambdaOrig) / lambda;
if (Math.abs(delta) < 1.0e-12) {
break;
}
}
double distance = (b * A * (sigma - deltaSigma));
results[0] = distance;
if (results.length > 1) {
double initialBearing = Math.atan2(cosU2 * sinLambda, cosU1 * sinU2
- sinU1 * cosU2 * cosLambda);
initialBearing *= 180.0 / Math.PI;
results[1] = initialBearing;
if (results.length > 2) {
double finalBearing = Math.atan2(cosU1 * sinLambda, -sinU1 * cosU2
+ cosU1 * sinU2 * cosLambda);
finalBearing *= 180.0 / Math.PI;
results[2] = finalBearing;
}
}
return results;
}
//computeDestinationAndBearing(double lat1, double lon1,double brng, double dist)
public static double[] computeDestinationAndBearing(double lat1, double lon1,
double brng, double dist) {
double results[] = new double[3];
double a = 6378137, b = 6356752.3142, f = 1 / 298.257223563; // WGS-84
// ellipsiod
double s = dist;
double alpha1 = toRad(brng);
double sinAlpha1 = Math.sin(alpha1);
double cosAlpha1 = Math.cos(alpha1);
double tanU1 = (1 - f) * Math.tan(toRad(lat1));
double cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)), sinU1 = tanU1 * cosU1;
double sigma1 = Math.atan2(tanU1, cosAlpha1);
double sinAlpha = cosU1 * sinAlpha1;
double cosSqAlpha = 1 - sinAlpha * sinAlpha;
double uSq = cosSqAlpha * (a * a - b * b) / (b * b);
double A = 1 + uSq / 16384
* (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
double sinSigma = 0, cosSigma = 0, deltaSigma = 0, cos2SigmaM = 0;
double sigma = s / (b * A), sigmaP = 2 * Math.PI;
while (Math.abs(sigma - sigmaP) > 1e-12) {
cos2SigmaM = Math.cos(2 * sigma1 + sigma);
sinSigma = Math.sin(sigma);
cosSigma = Math.cos(sigma);
deltaSigma = B
* sinSigma
* (cos2SigmaM + B
/ 4
* (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6
* cos2SigmaM * (-3 + 4 * sinSigma * sinSigma)
* (-3 + 4 * cos2SigmaM * cos2SigmaM)));
sigmaP = sigma;
sigma = s / (b * A) + deltaSigma;
}
double tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1;
double lat2 = Math.atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1,
(1 - f) * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp));
double lambda = Math.atan2(sinSigma * sinAlpha1, cosU1 * cosSigma - sinU1
* sinSigma * cosAlpha1);
double C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
double L = lambda
- (1 - C)
* f
* sinAlpha
* (sigma + C * sinSigma
* (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
double lon2 = (toRad(lon1) + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise
// to
// -180...+180
double revAz = Math.atan2(sinAlpha, -tmp); // final bearing, if required
results[0] = toDegrees(lat2);
results[1] = toDegrees(lon2);
results[2] = toDegrees(revAz);
return results;
}
private static double toRad(double angle) {
return angle * Math.PI / 180;
}
private static double toDegrees(double radians) {
return radians * 180 / Math.PI;
}
//computeIntersectionPoint(LatLng p1, double brng1, LatLng p2, double brng2)
public static LatLng computeIntersectionPoint(LatLng p1, double brng1, LatLng p2, double brng2) {
double lat1 = toRad(p1.latitude), lng1 = toRad(p1.longitude);
double lat2 = toRad(p2.latitude), lng2 = toRad(p2.longitude);
double brng13 = toRad(brng1), brng23 = toRad(brng2);
double dlat = lat2 - lat1, dlng = lng2 - lng1;
double delta12 = 2 * Math.asin(Math.sqrt(Math.sin(dlat / 2) * Math.sin(dlat / 2)
+ Math.cos(lat1) * Math.cos(lat2) * Math.sin(dlng / 2) * Math.sin(dlng / 2)));
if (delta12 == 0) return null;
double initBrng1 = Math.acos((Math.sin(lat2) - Math.sin(lat1) * Math.cos(delta12)) / (Math.sin(delta12) * Math.cos(lat1)));
double initBrng2 = Math.acos((Math.sin(lat1) - Math.sin(lat2) * Math.cos(delta12)) / (Math.sin(delta12) * Math.cos(lat2)));
double brng12 = Math.sin(lng2 - lng1) > 0 ? initBrng1 : 2 * Math.PI - initBrng1;
double brng21 = Math.sin(lng2 - lng1) > 0 ? 2 * Math.PI - initBrng2 : initBrng2;
double alpha1 = (brng13 - brng12 + Math.PI) % (2 * Math.PI) - Math.PI;
double alpha2 = (brng21 - brng23 + Math.PI) % (2 * Math.PI) - Math.PI;
double alpha3 = Math.acos(-Math.cos(alpha1) * Math.cos(alpha2) + Math.sin(alpha1) * Math.sin(alpha2) * Math.cos(delta12));
double delta13 = Math.atan2(Math.sin(delta12) * Math.sin(alpha1) * Math.sin(alpha2), Math.cos(alpha2) + Math.cos(alpha1) * Math.cos(alpha3));
double lat3 = Math.asin(Math.sin(lat1) * Math.cos(delta13) + Math.cos(lat1) * Math.sin(delta13) * Math.cos(brng13));
double dlng13 = Math.atan2(Math.sin(brng13) * Math.sin(delta13) * Math.cos(lat1), Math.cos(delta13) - Math.sin(lat1) * Math.sin(lat3));
double lng3 = lng1 + dlng13;
return new LatLng(toDegrees(lat3), (toDegrees(lng3) + 540) % 360 - 180);
}
I will suggest you to go through the the above site and get the knowledge as i had also done the same.
Hope this may help , i know the is not in ios but the concept is same as i done my project by changing code of javascript.
Cheers !!!
My requirement was something similar to this. I ended up writing up my own algo for this. https://github.com/RanaRanvijaySingh/PolygonBuffer
All you need to use is this line
double distance = 0.0001;
List bufferedPolygonList = AreaBuffer.buffer(pointList, distance);
It gives you a list of buffered polygon points at a given distance from your original polygon.
I would recommend to use Turf.js library for buffering and many basic gis operations. You would be able to retrieve each edge from the path that returned. For geometry buffer, it is easy to use, quite light weighted and it works without any problem for my applications using MapBox.js or leaflet.
More details : Turf.js Buffer
But if you are looking for a geodesic distance buffer that could be problem. I would use Arcgis Javascript API
Take a look at BOOST this is a big C++ library, you may find library/source code for almost everything up there like, buffer methods with different types such as miter,round,square.
Just install the latest version of the Boost which I guess is 1.58.0 right now, and take a look at BOOST/Geometry/Strategies/Cartesian/buffer[Something]-Square/Miter/Round
Here it is a good document
You need to convert your geodetic coordinates (lat/long) to cartesian (x/y) and use the Boost library and reverse the conversion. you do not need to use ArcGIS or any other GIS library at all.

converting isometric tile map coordinates to screen coordinates

I'm trying to convert isometric tile coordinates to screen coordinates.
I seem to have problem especially with the Y coordinates, looks like the X part works just fine. here is what I got so far.
// calculate screen coordinates from tile coordinates
- (CGPoint)positionForTileCoord:(CGPoint)pos {
float halfMapWidth = _tileMap.mapSize.width*0.5;
float mapHeight = _tileMap.mapSize.height;
float tileWidth = _tileMap.tileSize.width;
float tileHeight = _tileMap.tileSize.height;
int x = halfMapWidth*tileWidth + tileWidth*pos.x*0.5-tileWidth*pos.y*0.5;
int y = ............
return ccp(x, y);
my player is added as a child to the Tile map itself and the map is added to the layer at screenSize.x/2, scrrensize.y/2 with an anchor point of 0.5
I have done the same thing successfully with an orthogonal map but seem to struggle with the isometric one.
Thank you
really its look like this:
// calculate screen coordinates from tile coordinates
- (CGPoint)positionForTileCoord:(CGPoint)pos {
float halfMapWidth = _tileMap.mapSize.width*0.5;
float mapHeight = _tileMap.mapSize.height;
float tileWidth = _tileMap.tileSize.width;
float tileHeight = _tileMap.tileSize.height;
int x = halfMapWidth*tileWidth + tileWidth*pos.x*0.5-tileWidth*pos.y*0.5;
int y = (pos.y + (mapHeight * tileWidth/2) - (tileHeight/2)) - ((pos.y + pos.x) * tileHeight/2) + tileHeight;
return ccp(x, y);
}
// calculating the tile coordinates from screen location
-(CGPoint) tilePosFromLocation:(CGPoint)location
{
CGPoint pos = location;
float halfMapWidth = _tileMap.mapSize.width*0.5;
float mapHeight = _tileMap.mapSize.height;
float tileWidth = _tileMap.tileSize.width;
float tileHeight = _tileMap.tileSize.height;
CGPoint tilePosDiv = CGPointMake(pos.x/tileWidth, pos.y/tileHeight);
float invereseTileY = mapHeight - tilePosDiv.y;
// Cast int to make sure that result is in whole numbers
float posX = (int)(invereseTileY + tilePosDiv.x - halfMapWidth);
float posY = (int)(invereseTileY - tilePosDiv.x + halfMapWidth);
return CGPointMake(posX, posY);
}
int y = (pos.y + (mapHeight * tileWidth/2) - (tileHeight/2)) - ((pos.y + pos.x) * tileHeight/2) + tileHeight;

Calculate Distance Formula while using a Scale

I am trying to calculate a distance based upon the scale my users draws in.
My user can snap to other points in the game by drawing a line on a grid.
The grid is a numerical value that they can select 8, 16, 24, or 32.
The scale can be changed by the user selecting a whole number (1-10) and fraction number (0, 0.5, 0.75, or 0.125).
The user can select between showing the distance in Metric or Empirical units.
I am having difficulty spitting out the distance once the scale is changed.
Can anyone tell me where I have gone wrong in my math?
- (double) distanceFormula : (float) x1 : (float) y1 : (float) x2 : (float) y2 {
// 1 meter * 3.280839895 feet => feet
// 1 foot * 1 meter/3.280839895 feet => meter
/* Use Pythagora's theorem to calculate distance */
double dx = (x2-x1);
double dy = (y2-y1);
double dist = sqrt(dx*dx + dy*dy);
NSLog(#"Raw Distance %f", dist) ;
return dist;
}
- (NSString *) returnDistanceAsString : (double) distance {
NSString * string;
double d = distance / [self returnGridSize];
double scale = [self returnScaleWhole] + [self returnScaleSub];
if ([self returnUseMetric]) {
//METRIC
int tempCentim = (d * kCMConst) / 2;
if (tempCentim < 1) {
string = [NSString stringWithFormat:#"%d mm", tempCentim];
} else if (tempCentim > 1) {
string = [NSString stringWithFormat:#"%d mm", tempCentim];
} else if (tempCentim > 100) {
//eventually going to add cm mm
}
} else {
//EMPERICAL
int RL = d * scale;
int feet = RL / 12.0;
int inches = (int)RL % 12;
string = [NSString stringWithFormat:#"%i' %i\"", feet, inches];
}
return string;
}
The main questionable thing I noticed is that, in metric, you're neglecting to multiply by scale, but you are multiplying by kCMConst/2. You don't explain what kCMConst is, but it seems unlikely to be a value that needs to be divided by 2.

How to translate a given coordinate into a bounding box of "diameter" x?

For example, I have a latitude and longitude in decimal format (as opposed to Degrees-Hours-Minutes like lat=44.1° 9.5' 30''). To search for nearby objects, I must specify the search "radius" as a rectangle with four values:
north = 44.1;
south = -9.9;
east = -22.4;
west = 55.2;
Is there a formula or rule of thumb how to convert decimal lat/long values into a rectangular bounding box, such that the given latitude/longitude is in the center of that box?
Must I fiddle around myself with a WGS84 ellipsoid algorithm or are there open solutions to the problem?
I had exactly this problem and the solution isn't that straight forward, but the good news is that after a lot of work (and a great deal of help from SO and Google) I think I've cracked it.
There are lots of libraries around such as Proj4 which offer a multitude of algorithms to perform the required transformations, but coming at it cold I found it all a bit confusing and ended up writing my own code (I always like to know how things work).
My solution is based on ECEF and it works like this...
As I'm sure you've figured out, lines of latitude are always the same distance apart (the distance between 10 degrees and 20 degrees is the same as that between 20 and 30), but lines of longitude converge to meet at the poles. So the distance between 10 degrees and 20 degrees longitude at the equator is much larger than near the poles (and is 0 at the poles).
So you can easily work out how many metres between 2 degrees of latitude, but to do this with longitude you have to take the latitude into account.
Near the equator 1 degree of lat is pretty much the same distance as 1 degree of long, so if the map we're projecting had it's centre (0, 0) we can simply multiply lat and long by a constant to get metres from the map centre for any given point.
So my algorithm effectively rotates the globe until the actual centre of the map is at 0, 0.
So say the centre is really at (50.52, -4.82) - which it is in my case.
Imagine you're holding a globe and looking down on it with 0 lat, 0 long directly below you in the visible centre.
What we need to do is take our globe which currently has (0, 0) directly below us and rotate it in a westward (to the right) direction until (0, -4.82) is below us.
Then we rotate the globe southward (down) until (50.52, -4.82) is below us.
As a third step, we may then want to rotate it clockwise or anti-clockwise to correct for the orientation of the map with respect to true north (if true north is straight up on your map or if all you are interested in is distance not bearing, you won't need to do this)
So conceptually that's what we need to do, but how does that relate to our algorithm?
The answer is a transform (class) where we feed in three angles of rotation. This class has a public function which, given a lat/long pair, will return a new lat/long pair of that point on the globe after rotation.
And once we've done that, knowing the radius of the earth, we can convert this new pair into x and y coordinates, representing the distance from our map origin.
I should mention here that the earth is wider at the equator than it is at the poles, but the maths to deal with this is quite simply not worth the bother. However you calculate your x, y coords they will always be slightly out since the earth is not flat and for me, the code presented below does the job.
If your map is very close to the poles I suspect the results from this algorith may become quite inaccurate - basically lat/long doesn't really work very well at the poles (just take a look at google earth from above).
The MapTransform class requires you to setup a few things.
setRadius(1000); sets up the transform to work with a sphere of radius 1000 (units)
setBody("EARTH"); sets up the transform with the mean radius of the earth (in metres)
setRotation(x, y, z); sets up the transform to rotate about Z axis by z degrees, Y axis by y degrees then X axis by x degrees.
- basically, given your centre point (lat, long) and given that true north on the map is straight up, you would need the following: setRotation(0, lat, -long);
- the order of rotation is very important here and based on a coordinate system (looking back at the globe you're holding) where the Z axis coincides with the rotation of the earth, the Y axis rotates the closest surface of the globe up/down and the X axis is the axis you are looking along - hope this makes sense, it's a difficult concept to describe - see Rotation Matrix
Given your requirement to map from lat/long to metres from a specific point, the above should be all you need.
The function getMapPosition(lat, long) will return a double[] containing x, y in map units (metres if radius was specified in metres) from your origin
My class goes a bit further in terms of applying the coordinates to a specific map tile...
setMapOrigin(x, y); sets up where the rotational origin of the map (the point directly below the observer after rotation) is in relation to the bottom left corner of your map. Nominally this should be in metres (certainly if you used setBody("EARTH");) but needs to be in the same units as the specified radius.
setMapSize(w, h); sets up the size of the map in metres or what ever units you decided to use.
Finally, setBitmapSize(w, h) allows you to describe the size of the bitmap (in pixels) onto which you are projecting your map. In my application I have a bitmap representation of the map area and use the transform to supply the exact coorinates of the pixel on my bitmap where a point should be plotted. However, this isn't part of the question you asked so you may not need it.
Really hope this helps - seems just as long winded and complicated as all the examples I was looking at a month ago now.
import java.text.DecimalFormat;
public class MapTransform {
private double circumference;
private RotationMatrix rotationMatrix;
private double originX;
private double originY;
private double mapWidth;
private double mapHeight;
private int bitmapWidth;
private int bitmapHeight;
public MapTransform() {
this.circumference = 0;
this.rotationMatrix = new RotationMatrix();
this.rotationMatrix.makeIdentity();
this.originX = 0;
this.originY = 0;
this.mapWidth = 0;
this.mapHeight = 0;
this.bitmapWidth = 0;
this.bitmapHeight = 0;
}
public void setCircumference(double circumference) {
this.circumference = circumference;
}
public void setRadius(double radius) {
this.circumference = 2 * Math.PI * radius;
}
public void setBody(String body) {
if (body.toUpperCase().equals("EARTH")) {
setRadius(6371009); //mean radius of the earth in metres
// setRadius(6378137); //equatorial radius of the earth in metres
// setRadius(6356752); //polar radius of the earth in metres
}
else {
setRadius(0);
}
}
public void setRotation(double xRotateDegrees, double yRotateDegrees, double zRotateDegrees) {
RotationMatrix xMatrix = new RotationMatrix();
RotationMatrix yMatrix = new RotationMatrix();
RotationMatrix zMatrix = new RotationMatrix();
xMatrix.makeRotateX(Math.toRadians(xRotateDegrees));
yMatrix.makeRotateY(Math.toRadians(yRotateDegrees));
zMatrix.makeRotateZ(Math.toRadians(zRotateDegrees));
this.rotationMatrix = zMatrix.concatenate(yMatrix).concatenate(xMatrix);
}
public void setMapOrigin(double originX, double originY) {
this.originX = originX;
this.originY = originY;
}
public void setMapSize(double width, double height) {
this.mapWidth = width;
this.mapHeight = height;
}
public void setBitmapSize(int width, int height) {
this.bitmapWidth = width;
this.bitmapHeight = height;
}
public double[] getMapPosition(double[] geoPosition) {
return getMapPosition(geoPosition[0], geoPosition[1]);
}
public double[] getMapPosition(double latitude, double longitude) {
// convert the GeoPosition into an NVector
NVector vec = new NVector(latitude, longitude);
// rotate the vector in 3D
vec = rotationMatrix.transform(vec);
// convert the vector into 2D units by applying circumference to latitude/longitude and adding origins
double x = vec.getLongitude() * this.circumference / 360;
double y = vec.getLatitude() * this.circumference / 360;
// return a MapPosition
return new double[] {x, y};
}
public float[] getPixelPosition(double[] mapPosition) {
return getPixelPosition(mapPosition[0], mapPosition[1]);
}
public float[] getPixelPosition(double mapX, double mapY) {
// apply origin and scale based on map and bitmap widths
float x = (float) ((this.originX + mapX) * this.bitmapWidth / this.mapWidth);
// apply origin and scale based on map and bitmap heights, but invert to measure from top left instead of bottom left
float y = (float) (this.bitmapHeight - (this.originY + mapY) * this.bitmapHeight / this.mapHeight);
return new float[] {x, y};
}
public class RotationMatrix {
String name = "";
public double array [][] = {{0,0,0},{0,0,0},{0,0,0}};
public RotationMatrix() {}
public RotationMatrix(String name) {
this.name = name;
}
public void makeIdentity() {
for(int x = 0; x <= 2; x++) {
for (int y = 0; y <= 2; y++) {
array[x][y] = (x == y)? 1: 0;
}
}
}
public void makeRotateX(double thetaRadians) {
double cosTheta = Math.cos(thetaRadians);
double sinTheta = Math.sin(thetaRadians);
makeIdentity();
array[1][1] = cosTheta;
array[2][1] = -sinTheta;
array[1][2] = sinTheta;
array[2][2] = cosTheta;
}
public void makeRotateY(double thetaRadians) {
double cosTheta = Math.cos(thetaRadians);
double sinTheta = Math.sin(thetaRadians);
makeIdentity();
array[0][0] = cosTheta;
array[2][0] = sinTheta;
array[0][2] = -sinTheta;
array[2][2] = cosTheta;
}
public void makeRotateZ(double thetaRadians) {
double cosTheta = Math.cos(thetaRadians);
double sinTheta = Math.sin(thetaRadians);
makeIdentity();
array[0][0] = cosTheta;
array[1][0] = -sinTheta;
array[0][1] = sinTheta;
array[1][1] = cosTheta;
}
public NVector transform(NVector vec) {
NVector vec2 = new NVector();
vec2.x = vec.x * array[0][0] + vec.y * array[1][0] + vec.z * array[2][0];
vec2.y = vec.x * array[0][1] + vec.y * array[1][1] + vec.z * array[2][1];
vec2.z = vec.x * array[0][2] + vec.y * array[1][2] + vec.z * array[2][2];
return vec2;
}
public void output() {
if (this.name != null && this.name.length() == 0) {
System.out.println(this.name + "-------");
}
DecimalFormat df = new DecimalFormat("0.00");
for(int y = 0; y <= 2; y++) {
String out = "| ";
double test = 0;
for(int x = 0; x <= 2; x++) {
String f = df.format(array[x][y]);
if (f.length() < 5) f = " " + f;
out += f + " ";
test = test + array[x][y] * array[x][y];
}
if (test > 0.99 && test < 1.01) {test = 1.0;}
out += "| (=" + test + ")";
System.out.println(out);
}
System.out.println();
}
public RotationMatrix concatenate(RotationMatrix m2) {
RotationMatrix outputMatrix = new RotationMatrix();
for(int x = 0; x <= 2; x++) {
for(int y = 0; y <=2; y++) {
outputMatrix.array[x][y] = 0;
for (int q = 0; q <= 2; q++) {
outputMatrix.array[x][y] += this.array[x][q] * m2.array[q][y];
}
}
}
return outputMatrix;
}
}
public class NVector {
double x;
double y;
double z;
public NVector() {
this.x = 0;
this.y = 0;
this.z = 0;
}
public NVector(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public NVector(double latitude, double longitude) {
setLatitudeLongitude(latitude, longitude);
}
public NVector(double[] geoPosition) {
setLatitudeLongitude(geoPosition[0], geoPosition[1]);
}
private void setLatitudeLongitude(double latitude, double longitude) {
double latitudeRadians = Math.toRadians(latitude);
double longitudeRadians = Math.toRadians(longitude);
double cosLatitude = Math.cos(latitudeRadians);
double cosLongitude = Math.cos(longitudeRadians);
double sinLatitude = Math.sin(latitudeRadians);
double sinLongitude = Math.sin(longitudeRadians);
this.x = cosLatitude * cosLongitude;
this.y = cosLatitude * sinLongitude;
this.z = sinLatitude;
}
public double getLatitude() {
return Math.toDegrees(Math.atan2(this.z, Math.sqrt(this.x * this.x + this.y * this.y)));
}
public double getLongitude() {
return Math.toDegrees(Math.atan2(this.y, this.x));
}
public double[] getGeoPosition() {
double[] geoPosition = new double[] {this.getLatitude(), this.getLongitude()};
return geoPosition;
}
public void output() {
output("");
}
public void output(String name) {
if (name != null && name.length() == 0) {
System.out.println("NVector: " + name);
}
DecimalFormat df = new DecimalFormat("0.00");
String vector = df.format(this.x) + "," + df.format(this.y) + "," + df.format(this.z);
String coords = "";
try {
coords = df.format(Math.toDegrees(this.getLatitude())) + "N " + df.format(Math.toDegrees(this.getLongitude())) + "E";
}
catch(Exception e) {
coords = "(coords unknown)";
}
System.out.println("(" + vector + ") at " + coords);
}
}
}
If you're talking about coordinates on a globe, isn't there really no clear definition of "rectangular bounding box" on a manifold?
Couldn't you just approximate the center of the "box" by averaging the dimensions of the rectagle as in cartesian coordinates:
x_center = x_left + (x_right - x_left) / 2
y_center = y_bottom + (y_top - y_bottom) / 2
Why not do a range/bearing from your center point to define the lat/lon's of the corner of the box (if that is what you are asking)? Use the four bearings 45 deg, 135 deg, 225 deg, 315 deg. See this web site for "Destination point from range/bearing": http://www.movable-type.co.uk/scripts/latlong.html
Another, much shorter answer to my other post if you just need the distance to another lat long from your point of origin which I found on SO (surprise).
Original answer was here, but I believe the code you're looking for is...
public static double distFrom(double lat1, double lng1, double lat2, double lng2) {
double earthRadius = 6371009; //mean radius of the earth in metres
double dLat = Math.toRadians(lat2-lat1);
double dLng = Math.toRadians(lng2-lng1);
double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
Math.sin(dLng/2) * Math.sin(dLng/2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
double dist = earthRadius * c;
return dist;
}

Resources