I'm trying to use Miller Projection to convert coordinates to pixels.
My method looks like this:
function millerProjection(lat, lng) {
// Create sec() function //
function sec(value) {
return 1/Math.cos(value);
}
// Create fucntion to change degree to radian //
function toRadian(value) {
return value * Math.PI / 180;
}
lng = toRadian(lng);
lat = toRadian(lat);
// Miller Projection
// var x = lng;
// var y = 1.25 * Math.log(Math.tan(Math.PI / 4 + 0.4 * (lat)));
// Mercator Projection
// var x = lng;
// var y = Math.log(Math.tan(lat) + sec(lat));
var mapSet = {
leftLong: toRadian(-180),
rightLong: toRadian(180),
topLat: toRadian(90),
bottomLat: toRadian(-90),
imageWidth: 2057,
imageHeight: 1512,
}
var x = (lng - mapSet.leftLong) * (mapSet.imageWidth / (mapSet.rightLong - mapSet.leftLong));
var y = (mapSet.topLat - lat) * (mapSet.imageHeight / (mapSet.topLat - mapSet.bottomLat));
console.log(`Miller Projection X: ${x} -- Y: ${y}`);
return { x: x, y: y };
}
I'm using this picture as a map:
https://upload.wikimedia.org/wikipedia/commons/5/5f/Miller_projection_SW.jpg
Apparently If I use 0, 0 coordinates it marks the correct location.
If I give it any other coordinates it's not working. Can the map be the problem or maybe there is an issue with the logic I use?
This:
var x = (lng - mapSet.leftLong) * (mapSet.imageWidth / (mapSet.rightLong - mapSet.leftLong));
var y = (mapSet.topLat - lat) * (mapSet.imageHeight / (mapSet.topLat - mapSet.bottomLat));
Assumes a linear x and y transform for both latitude and longitude, but this only occurs for longitude. You can see the different spacings between latitudes in the image you reference.
Let's go back to your commented out projection and use that as it is correct, but needs scaling and translation:
function millerProjection(lat, lng) {
// Create sec() function
function sec(value) {
return 1/Math.cos(value);
}
// Create fucntion to change degree to radians
function toRadian(value) {
return value * Math.PI / 180;
}
lng = toRadian(lng);
lat = toRadian(lat);
// Miller Projection
var x = lng;
var y = 1.25*Math.log(Math.tan(Math.PI/4+0.4*(lat)));
return [x,y];
}
console.log(millerProjection(90,180));
console.log(millerProjection(-90,-180));
The output range for the x axis is -π to + π, and the y axis has a range about 0.733 times that.
Now we can scale and translate. I'll scale first and translate later, but vice versa is just as easy.
To scale, we need to know the width or height of the bounding box or output range. The aspect is fixed, so this is easiest if not specifying both, but rather determining one from the other. If we stretch the outputs unevenly, we no longer have a Miller.
Given a dimension for width we might scale with something like:
var scale = width / Math.PI / 2;
We want to see how many pixels are needed for each radian. Then we can multiple the projection output by the scale to get scaled values. Using the above, we can also validate our projection using a library like d3's geoprojection module:
function millerProjection(lat, lng) {
// Create sec() function
function sec(value) {
return 1/Math.cos(value);
}
// Create fucntion to change degree to radians
function toRadian(value) {
return value * Math.PI / 180;
}
lng = toRadian(lng);
lat = toRadian(lat);
// Miller Projection
var x = lng;
var y = -1.25*Math.log(Math.tan(Math.PI/4+0.4*(lat)));
var width = 360;
var scale = width/Math.PI/2;
x *= scale;
y *= scale;
return [x,y];
}
////
// set up reference scale:
var width = 360;
var height = width / Math.PI / 2.303412543376391; // aspect ratio
// set d3 projection for reference:
var d3Miller = d3.geoMiller()
.fitSize([360,180*0.7331989845], {"type": "Polygon","coordinates": [[[180, 90], [-180, 90], [-90, -180], [180, 90]]] })
.translate([0,0]);
// compare the two:
console.log("90N,180W:")
console.log("Miller: ", ...millerProjection(90,-180));
console.log("d3Miller:", ...d3Miller([-180,90]));
console.log("90S,180E:")
console.log("Miller: ",...millerProjection(-90,180));
console.log("d3Miller:", ...d3Miller([180,-90]));
console.log("90S,180E:")
console.log("Miller: ",...millerProjection(-57,162));
console.log("d3Miller:", ...d3Miller([162,-57]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-geo.v1.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
I've taken the negative of the latitude (based on your example) because while projected geographic coordinates have y = 0 at the bottom - with y values increasing as one moves up. Conversely, things like (most?) images have y = 0 at the top - with y values increasing as one moves down. D3 anticipates the latter convention so I did not do it for the reference function
Looks good. Our data now has a range of -width/2 to width/2 on the x axis, and again on the y, about 0.733 times that. Let's translate the data so that it occupies the bounding box of with a bottom left coordinate of [0,0] and a top right coordinate of [width,width*0.733]. This is fairly easy, after we scale the data, we add width/2 to the x value, and width/2*0.733 (or slightly more precisely, width/2/*0.7331989845) to the y value:
function millerProjection(lat, lng) {
// Create sec() function
function sec(value) {
return 1/Math.cos(value);
}
// Create fucntion to change degree to radians
function toRadian(value) {
return value * Math.PI / 180;
}
lng = toRadian(lng);
lat = toRadian(lat);
// Miller Projection
var x = lng;
var y = -1.25*Math.log(Math.tan(Math.PI/4+0.4*(lat)));
var width = 2057;
var scale = width/Math.PI/2;
x *= scale;
y *= scale;
x += width/2;
y += width/2*0.7331989845
return [x,y];
}
// compare the two:
console.log("90N,180W:")
console.log("Miller: ", ...millerProjection(90,-180));
console.log("90S,180E:")
console.log("Miller: ",...millerProjection(-90,180));
console.log("45N,45E:")
console.log("Miller: ",...millerProjection(45,45));
console.log("90S,180E:")
console.log("Miller: ",...millerProjection(-57,162));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-geo.v1.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
Of course, if you are doing image manipulation where the top should be y=0 and y values increase if you move down, flip the sign of the latitude before doing any manipulation.
Related
I want to generate a Terrain in Unity from SRTM data. Width and length of the terrain is specified. The terrain itself is 3061 x 2950 (width, length). A smaller area starting from (0,0) to (~2300, ~2130) has the correct terrain. The remainder is flat surface with 0 height.
here is an image of the problem
The relevant code:
LocalizedMercatorProjection mercator; // basic Mercator projection
SRTM_Reader srtm; // wrapper around https://github.com/itinero/srtm with SRTM data "N49E009.hgt"
// coordinate bounds of Flein, Germany
public float max_lat = 49.1117000f;
public float min_lat = 49.0943000f;
public float min_lon = 9.1985000f;
public float max_lon = 9.2260000f;
public void GenerateTerrain()
{
ConfigureTerrainData();
float[,] heights = GenerateTerrainData();
terrain.terrainData.SetHeights(0, 0, heights);
}
void ConfigureTerrainData()
{
this.depth = Math.Abs(Convert.ToInt32(mercator.latToY(max_lat) - mercator.latToY(min_lat)));
// this.depth = 2950;
this.width = Math.Abs(Convert.ToInt32(mercator.lonToX(max_lon) - mercator.lonToX(min_lon)));
// this.width = 3061;
this.height = 400;
this.terrain.terrainData.heightmapResolution = width + 1;
this.terrain.terrainData.size = new Vector3(width, height, depth);
}
float[,] GenerateTerrainData()
{
float[,] heights = new float[depth, width];
for(int x = 0; x < (width); x += 1)
{
for (int y = 0; y < (depth); y += 1)
{
heights[y,x] = CalculateHeight(x, y);
}
}
return heights;
}
float CalculateHeight(int x, int y)
{
// uses SRTMData.GetElevationBilinear(lat, lon) under the hood
float elevation = srtm.GetElevationAtSync(mercator.yToLat(y), mercator.xToLon(x));
if (elevation >= height) return 1.0f;
return elevation / height;
}
Does anyone have an idea why only a smaller area of the terrain is filled?
Edit 1: Setting the values for depth and width to 4097 mitigates the problem. This is not a perfect solution to me, so the question still persists.
I am creating a simple navigation application using GoogleMap.
When the user set the start location and destination location I am drawing the path using polylines.
If the user deviate from the path, I want to redraw the path based on current user location.
Problem I have is how to detect user is not in the current route?
I am using
https://maps.googleapis.com/maps/api/directions/json?
to get the directions.
Is there a function in that to detect whether user is outside the route?
I also have the list of coordinates used to draw the poly lines.
Can they be used to detect user moved outside the current route ?
Based on an example I found in web, I created this method. The example is not designed to for the map coordinates.
Function for finding the distance between a point and an edge in java
Is there a better way of doing this?
static double perpendicularDistance(LatLng point, LatLng start, LatLng end) {
double x = point.longitude;
double y = point.latitude;
double x1 = start.longitude;
double y1 = start.latitude;
double x2 = end.longitude;
double y2 = end.latitude;
double A = x - x1;
double B = y - y1;
double C = x2 - x1;
double D = y2 - y1;
double dot = A * C + B * D;
double len_sq = C * C + D * D;
double param = -1;
if (len_sq != 0) //in case of 0 length line
param = dot / len_sq;
double xx, yy;
if (param < 0) {
xx = x1;
yy = y1;
} else if (param > 1) {
xx = x2;
yy = y2;
} else {
xx = x1 + param * C;
yy = y1 + param * D;
}
var dx = x - xx;
var dy = y - yy;
// one degree is equivalent to 111km approximately
return (sqrt(dx * dx + dy * dy)).abs() * 111000;
}
May be you need to get coordinates of user everytime he/she walks, and check if that coordinates lies in the list of coordinates which you have.
You can get location using https://pub.dev/packages/location.
These is just a thought, I can be wrong.
You can use this package
https://pub.dev/packages/google_maps_utils
with this method to check if you are not near of your route:
/// Computes whether the given point lies on or near a polyline, within a specified
/// tolerance in meters. The polyline is composed of great circle segments if geodesic
/// is true, and of Rhumb segments otherwise. The polyline is not closed -- the closing
/// segment between the first point and the last point is not included.
static bool isLocationOnPathTolerance(Point point, List<Point> polyline,
bool geodesic, double tolerance
)
Where point is, a class to represent two dimensional positions, for example;
var rightBottom = const Point(200, 400);
As you can see, you can send the tolerance for the distance in meters.
I have a problem regarding positioning an image according to the touches location, however limited to a circle.
It works for the most part, but if the angle (from the touches location to the desired location) is less than 0, it positions the image on the wrong side of the circle.
Perhaps it's some maths that I've done wrong.
Anyway, here's the code:
float newHeight, newWidth, centerPointX, centerPointY;
newHeight = -(invertedY.y - (view.frame.origin.y+view.frame.size.height/2));
newWidth = -(invertedY.x - (view.frame.origin.x+view.frame.size.width/2));
float tangent = newHeight/newWidth;
float calculatedAngle = atanf(tangent);
float s, c, d, fX, fY;
d = view.frame.size.width/2+30;
if (calculatedAngle < 0) {
s = sinf(calculatedAngle) * d;
c = cosf(calculatedAngle) * d;
} else {
s = -sinf(calculatedAngle) * d;
c = -cosf(calculatedAngle) * d;
}
fX = view.center.x + c;
fY = view.center.y + s;
[delegate setPoint:CGPointMake(fX, fY)];
NSLog(#"angle = %.2f", calculatedAngle);
Any help appreciated.
I think the best way to limit location to a circle is calculate vector from center to touch location. Calculate vector length then divide it by that length so it would be normalized. Then multiply normalized vector by radius of circle and finally add this vector to the center to compute new location.
CGPoint touch, center;
CGPoint vector = CGPointMake(touch.x-center.x, touch.y-center.y);
float length = sqrtf(vector.x*vector.x + vector.y*vector.y);
// Normalize and multiply by radius (r)
vector.x = r * vector.x / length;
vector.y = r * vector.y / length;
[delegate setPoint:CGPointMake(center.x + vector.x, center.y + vector.y)];
How do I calculate the angle in degrees between the coordinates of two POIs (points of interest) on an iPhone map application?
I'm guessing you try to calculate the degrees between the coordinates of two points of interest (POI).
Calculating the arc of a great circle:
+(float) greatCircleFrom:(CLLocation*)first
to:(CLLocation*)second {
int radius = 6371; // 6371km is the radius of the earth
float dLat = second.coordinate.latitude-first.coordinate.latitude;
float dLon = second.coordinate.longitude-first.coordinate.longitude;
float a = pow(sin(dLat/2),2) + cos(first.coordinate.latitude)*cos(second.coordinate.latitude) * pow(sin(dLon/2),2);
float c = 2 * atan2(sqrt(a),sqrt(1-a));
float d = radius * c;
return d;
}
Another option is to pretend you are on cartesian coordinates (faster but not without error on long distances):
+(float)angleFromCoordinate:(CLLocationCoordinate2D)first
toCoordinate:(CLLocationCoordinate2D)second {
float deltaLongitude = second.longitude - first.longitude;
float deltaLatitude = second.latitude - first.latitude;
float angle = (M_PI * .5f) - atan(deltaLatitude / deltaLongitude);
if (deltaLongitude > 0) return angle;
else if (deltaLongitude < 0) return angle + M_PI;
else if (deltaLatitude < 0) return M_PI;
return 0.0f;
}
If you want the result in degrees instead radians, you have to apply the following conversion:
#define RADIANS_TO_DEGREES(radians) ((radians) * 180.0 / M_PI)
You are calculating the 'Bearing' from one point to another here. There's a whole bunch of formula for that, and lots of other geographic quantities like distance and cross-track error, on this web page:
http://www.movable-type.co.uk/scripts/latlong.html
the formulae are in several formats so you can easily convert to whatever language you need for your iPhone. There's also javascript calculators so you can test your code gets the same answers as theirs.
If the other solutions dont work for you try this:
- (int)getInitialBearingFrom:(CLLocation *)first
to:(CLLocation *)second
{
float lat1 = [self degreesToRad:first.coordinate.latitude];
float lat2 = [self degreesToRad:second.coordinate.latitude];
float lon1 = [self degreesToRad:first.coordinate.longitude];
float lon2 = [self degreesToRad:second.coordinate.longitude];
float dLon = lon2 - lon1;
float y = sin (dLon) * cos (lat2);
float x1 = cos (lat1) * sin (lat2);
float x2 = sin (lat1) * cos (lat2) * cos (dLon);
float x = x1 - x2;
float bearingRadRaw = atan2f (y, x);
float bearingDegRaw = bearingRadRaw * 180 / M_PI;
int bearing = ((int) bearingDegRaw + 360) % 360; // +- 180 deg to 360 deg
return bearing;
}
For final bearing, simply take the initial bearing from the end point to the start point and reverse it (using θ = (θ+180) % 360).
You need these 2 helpers:
-(float)radToDegrees:(float)radians
{
return radians * 180 / M_PI;
}
-(float)degreesToRad:(float)degrees
{
return degrees * M_PI /180;
}
I want to work out the distance between 2 latlon points.
The simple distance formula http://www.purplemath.com/modules/distform.htm is not correct because we are dealing with 2 different measures (lat and lon).
Is there a standard solution to this problem?
use Haversine formula.
see this link http://www.movable-type.co.uk/scripts/latlong.html
try this,
This uses the ‘haversine’ formula to calculate great-circle distances between the two points – that is, the shortest distance over the earth’s surface – giving an ‘as-the-crow-flies’ distance between the points (ignoring any hills!).
Haversine formula:
R = earth’s radius (mean radius = 6,371km)
Δlat = lat2− lat1
Δlong = long2− long1
a = sin²(Δlat/2) + cos(lat1).cos(lat2).sin²(Δlong/2)
c = 2.atan2(√a, √(1−a))
d = R.c
or go with the link,http://www.movable-type.co.uk/scripts/latlong.html
Try this javascript haversine function alongside the torad() helper function, which I use for my map app
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;
}
Hope this helps.