0

I wonder if anyone has done manual calculation to map a WGS84 coordinate to the UIView coordinates system? Indeed I convert WGS84 to ENU coordinate system which is similar to Cartesian (x,y,z) coordinate system.

I know there is a method in MKMapView which gets a CLLocationCoordinate2D and returns a CGPoint in an expected UIView, but I want to draw the CGPoints on a blank UIView rather than on a map. Therefore I'd like to skip initializing MKMapView object just to use this method.

There must be a simple way to scale down the coordinates from Cartesian to device coordinate system. I guess I also need to have some form of rotation/transformation for origin of the system (Cartesian -> UIView) as the origin point for UIView sits on top-left.

CppChase
  • 901
  • 12
  • 25
  • I'm guessing you have seen this... http://stackoverflow.com/questions/1185408/converting-from-longitude-latitude-to-cartesian-coordinates After that it's just a case of working out a scale factor to shrink down your coordinates and transform to bring them all in the view. I'm not sure which bit you're having trouble with though. – Fogmeister Mar 15 '16 at 16:10

2 Answers2

0

Shrinking down the coordinates should be straight forward.

Lets say you have a cluster of coordinates around a ridiculous point (32000, 192033). This is the centre point of that particular cluster.

Obviously we want to shift them so that they are centred around the origin (0, 0).

So take every point and convert it using...

newPoint = CGPointMake(oldPoint.x - 32000, oldPoint.y - 192033)

This will bring them all so that they are all the same relative distance apart but now centred on the origin.

Next, say the minimum X value is -9345 and the maximum X value is 8455.

You want your X range to be something like 640 (to fit all of them across the screen).

So...

currentXRange = 8455 + 9345; // maxX - minX = 17800
requiredXRange = 640;
xScale = requiredXRange / currentXRange; // 0.0359...

Now that you have an x scale you can convert your points by multiplying the x coordinate by the xScale value.

The minimum X value you have will be...

-9345 * xScale; // -335.4...

Maximum X value will be...

8455 * xScale; // 303.5...

EDIT so they are on screen

To bring them all so they are greater than 0. (i.e. so they are all on the screen) just subtract the smallest X and Y coordinate values from each of the points.

-335.4 - (-335.4) = 0 // minimum X value
303.5 - (-335.4) = 638.9 // maximum X value

That brings them all in range so that they fit on the screen.

EDIT upside down Y axis

To make sure they are displayed the right way up just create the points by subtracting them from the height of the view.

If the view size is (100, 100) then a point (50, 10) in iOS will be in the middle near the top of the view.

But in normal use it should be near the bottom.

So create the converted point using...

convertedPoint = CGPointMake(originalPoint.x, view.height - originalPoint.y)

This will create the point (50, 90) which will put it in the middle and ten points from the bottom like you wanted.

NOTE

As long as you do the same operation to every point in the set then they will all be in the same relative place and the data will still be valid.

The tricky part is working out the operation but I think I've covered everything.

Community
  • 1
  • 1
Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • Great. Thanks. I think we need to do 1 more thing. The x and y values I have are from ENU coordinate system and they are in meters (away from the reference point). I guess the conversion above assumes that we have CGPoints but they are off the display coordinate system. Therefore we must first convert the ENUs to CGPoints. There must be a relation between number of pixels for 1 meter. I think this must be different in different displays (e.g. retina displays has higher density) – CppChase Mar 15 '16 at 16:42
  • @CPPChase there shouldn't be any need for a conversion between metres and points. The numbers are abstract so if you get a value of 1000 metres then just treat it the same as 1000 points. For shrinking down for different devices just make sure that you use the view size that you want the points to fit onto. In this example I used the view width as 640 to find the xScale but if the view width was 320 it would just find a smaller xScale value. Just make sure to use the view size instead of a hard coded size. – Fogmeister Mar 15 '16 at 16:46
  • @CPPChase retina etc... Won't make a difference. Just use points and use the view.size values. – Fogmeister Mar 15 '16 at 16:47
  • Thanks a lot Fogmeister. – CppChase Mar 15 '16 at 16:51
  • @CPPChase no problem. Happy to help. – Fogmeister Mar 15 '16 at 16:54
0

@Fogmeister, eventhough your answer makes sense on the paper I don't know why I can't get it to work. I have positons in enus array. I first found the minx, miny, maxx, maxy

    let sortX = enus.sort({ $0.epos < $1.epos })
    let sortY = enus.sort({ $0.npos < $1.npos })
    let minx = sortX.first?.epos
    let maxx = sortX.last?.epos
    let miny = sortY.first?.npos
    let maxy = sortY.last?.npos

Then I found the ranges:

        let xrange = abs(maxx!-minx!)
        let yrange = abs(maxy!-miny!)

Then I found the scale factor:

        let xscale = self.frame.size.width.asDouble / xrange
        let yscale = self.frame.size.height.asDouble / yrange
        let factor = (xscale <= yscale ? xscale : yscale)

Then in a loop I use the factor and minx and miny to scale down and shift my enu positions:

    points.removeAll()
    let minx_scaled = minx * factor
    let miny_scaled = miny * factor
    for enu in enus {
        let e_scaled   = enu.epos * factor
        let n_scaled   = enu.npos * factor

        let x = e_scaled - minx_scaled
        var y = n_scaled - miny_scaled
        y = self.frame.size.height.asDouble - y

        let point = CGPointMake(x.asCGFloat,y.asCGFloat)
        points.append(point)
    }

and here I draw the points:

private let path1 = UIBezierPath()
var moved = false
override func drawRect(rect: CGRect) {

    if points.count == 0 { return}

    if moved == false {
        path1.moveToPoint(points.first!)
        moved = true
    }else{
        path1.addLineToPoint(points.last!)
    }

    path1.lineWidth = 3
    path1.lineJoinStyle = .Round
    UIColor.whiteColor().setStroke()
    path1.stroke()
}

The image below is a blank UIView where I draw my UIBezierPath. I used a little mapview as a reference.

enter image description here

CppChase
  • 901
  • 12
  • 25