I would like to ask you for help in following task.
What I need to do is to derive local x,y coordinates from latitude, longitude, applying stereographic projection of WGS84 ellipsoid on a plane, when the following parameters are known:
scale = 0.999790760649094
x0 = 41614.2107651061
y0 = 17150.1692507701
L0 = 18.4514121640352
B0 = 54.4353877827032
There's also a Q parameter but at the moment I hava no idea what is and what for it is:
Q = -0.0203604068846608
The above parameters are given by GPS data collecting software and become from site calibration using pairs of know [local cs] and surveyed [wgs84] points.
Example input coordinates:
latitude = 54.3324735043758
longitude = 18.6188610214872
Expected result:
x = 30395.719
y = 28273.712
Whether is this possible only with those parameters provided or I need something else?
Can it be done using Proj4 or pyproj?
If yes, what would be apropriate command?
If not, can you hint me how to do it by hand, like links to formulas etc, please?
Thanks in advance.
[edit] Adding some background to my question.
In the case if my question seems absurd...
As I mentioned above the parameters become from site calibration or GPS alignmnet using pairs of know and surveyed points.
I was told that after alignment is done, what I have to do to obtain x,y coordinates for any next surveyed wgs84 point, is to apply stereographic projection of WGS84 ellipsoid on a plane where:
B0, L0 are geodetic coordinates of the tangent point
X0, Y0 are grid coordinates of the tangent point
scale [k] is the scale factor
and with the least square method parameters of the transition to the local cs are determined.
Sorry if anything here is unclear but I am not quite familiar with subject.
Any help would be appreciated.
[edit]Adding additional sample points.
latitude longitude X Y
54.438657278 18.4602869845 41989.871 17718.469
54.4192483588 18.4806069996 39857.12 19081.45
54.4124384305 18.4716474826 39087.23 18515.701
54.3891511496 18.4799499633 36506.74 19108.41
54.3410205023 18.5443421778 31239.26 23406.7
54.3498711013 18.647572105 32374.58 30096.13
54.3515138914 18.6760425965 32600.628 31942.594
54.3800402814 18.6055277359 35669.507 27287.217
54.3971889473 18.6178833183 37596.017 28046.484
54.4041298674 18.6607429298 38432.47 30811.43
54.4070050071 18.6375394617 38717.65 29297.69
54.3609834076 18.474278312 33364.349 18804.941
[edit]Final word. Lests start from the beginning.
It turned out that my question was not quite right, in its original form.
So I decided to post full context and all steps I make in order to achieve the ultimate goal which is: getting local grid coordinates.
1.Site calibration points pairs.
latitude longitude x y
54.4353877827 18.451412164 41614.22 17150.17
54.3625019774 18.4469405743 33496.96 17024.81
54.3097484306 18.5670103377 27791.39 24957.15
54.3084348677 18.6299997771 27737.21 29059.27
54.402430173 18.6683595667 38254.91 31310.35
54.4230519006 18.6017101505 40450.41 26931.58
54.438657278 18.4602869845 41989.871 17718.469
54.4192483588 18.4806069996 39857.12 19081.45
54.4124384305 18.4716474826 39087.23 18515.701
54.3891511496 18.4799499633 36506.74 19108.41
54.3410205023 18.5443421778 31239.26 23406.7
54.3498711013 18.647572105 32374.58 30096.13
54.3515138914 18.6760425965 32600.628 31942.594
54.3800402814 18.6055277359 35669.507 27287.217
54.3971889473 18.6178833183 37596.017 28046.484
54.4041298674 18.6607429298 38432.47 30811.43
54.4070050071 18.6375394617 38717.65 29297.69
54.351702123 18.6831419293 32632.47 32403.62
54.4427340176 18.4391134326 42415.52 16335.76
54.4427368643 18.4592502382 42442.44 17641.9
54.3609834076 18.474278312 33364.349 18804.941
54.3714455799 18.7279691735 34899.27 35264.2
54.2911268152 18.6361668642 25820.32 29504.89
2.Convert above surveyed latitude and longitude to stereographic plane coordinates, taking coordinates of first calibration point as central latitude and longitude.
I don't know how important the scale factor [k_0] is so decided to force 1.0.
I also experimented with UTM instead of stereographic plane but result was not satisfactory with residuals = ~0.1m because we are talking here about obtaining survey grade coordinates not gis accuracy.
import pyproj
#input coordinates
p1 = pyproj.Proj(proj="latlong",datum="WGS84", ellps="WGS84")
#output result
p2 = pyproj.Proj(proj="stere", lat_0=54.4353877827032, lon_0=18.4514121640352, k_0=1.0, x_0=0, y_0=0, ellps="WGS84")
stereographic_coords= pyproj.transform(p2, p1, latitude, longitude)
print stereographic_coords[1], stereographic_coords[0] #keep N(=x), E(=y) order
The result:
N E
3.50449394074e-07 2.28385549832e-06
-8113.07087572 -290.673432675
-13978.9604246 7524.00783763
-14116.6372391 11624.2036175
-3646.95639951 14088.88636
-1362.75920089 9755.69391324
363.973973779 575.837791136
-1796.13420238 1895.18404979
-2554.36871405 1313.79387579
-5146.3420395 1853.8865397
-10500.2307161 6044.01338437
-9501.32640299 12755.170692
-9312.95200167 14605.8597464
-6149.937535 10013.9390815
-4239.26761866 10812.2644791
-3459.25317782 13593.690851
-3143.43494621 12086.0443555
-9290.50857805 15067.4007146
817.802325033 -797.917280294
818.077929071 508.518662863
-8281.8734697 1486.45713731
-7082.36334017 17973.5167497
-16042.1577585 12030.6541087
3.Similarity transformation.
We can perform now similarity transformation to obtain local coordinates for any given point. One thing to remember is we firstly need to convert given latitude, longitude to stereographic coordinates.
transformation.py
import math
import numpy as np
class GeometricTransform(object):
"""Perform geometric transformations on a set of coordinates.
"""
def __call__(self, coords):
raise NotImplementedError()
def inverse(self, coords):
raise NotImplementedError()
def __add__(self, other):
raise NotImplementedError()
class ProjectiveTransform(GeometricTransform):
_coeffs = range(8)
def __init__(self, matrix=None):
if matrix is None:
# default to an identity transform
matrix = np.eye(3)
if matrix.shape != (3, 3):
raise ValueError("invalid shape of transformation matrix")
self._matrix = matrix
@property
def _inv_matrix(self):
return np.linalg.inv(self._matrix)
def _apply_mat(self, coords, matrix):
coords = np.array(coords, copy=False, ndmin=2)
x, y = np.transpose(coords)
src = np.vstack((x, y, np.ones_like(x)))
dst = np.dot(src.transpose(), matrix.transpose())
# rescale to homogeneous coordinates
dst[:, 0] /= dst[:, 2]
dst[:, 1] /= dst[:, 2]
return dst[:, :2]
def __call__(self, coords):
return self._apply_mat(coords, self._matrix)
def inverse(self, coords):
return self._apply_mat(coords, self._inv_matrix)
def estimate(self, src, dst):
xs = src[:, 0]
ys = src[:, 1]
xd = dst[:, 0]
yd = dst[:, 1]
rows = src.shape[0]
# params: a0, a1, a2, b0, b1, b2, c0, c1
A = np.zeros((rows * 2, 9))
A[:rows, 0] = xs
A[:rows, 1] = ys
A[:rows, 2] = 1
A[:rows, 6] = - xd * xs
A[:rows, 7] = - xd * ys
A[rows:, 3] = xs
A[rows:, 4] = ys
A[rows:, 5] = 1
A[rows:, 6] = - yd * xs
A[rows:, 7] = - yd * ys
A[:rows, 8] = xd
A[rows:, 8] = yd
# Select relevant columns, depending on params
A = A[:, self._coeffs + [8]]
_, _, V = np.linalg.svd(A)
H = np.zeros((3, 3))
# solution is right singular vector that corresponds to smallest
# singular value
H.flat[self._coeffs + [8]] = - V[-1, :-1] / V[-1, -1]
H[2, 2] = 1
self._matrix = H
def __add__(self, other):
"""Combine this transformation with another.
"""
if isinstance(other, ProjectiveTransform):
# combination of the same types result in a transformation of this
# type again, otherwise use general projective transformation
if type(self) == type(other):
tform = self.__class__
else:
tform = ProjectiveTransform
return tform(other._matrix.dot(self._matrix))
else:
raise TypeError("Cannot combine transformations of differing "
"types.")
class SimilarityTransform(ProjectiveTransform):
"""2D similarity transformation of the form::
X = a0*x - b0*y + a1 =
= m*x*cos(rotation) + m*y*sin(rotation) + a1
Y = b0*x + a0*y + b1 =
= m*x*sin(rotation) + m*y*cos(rotation) + b1
where ``m`` is a zoom factor and the homogeneous transformation matrix is::
[[a0 b0 a1]
[b0 a0 b1]
[0 0 1]]
Parameters
----------
matrix : (3, 3) array, optional
Homogeneous transformation matrix.
scale : float, optional
Scale factor.
rotation : float, optional
Rotation angle in counter-clockwise direction as radians.
translation : (tx, ty) as array, list or tuple, optional
x, y translation parameters.
"""
def __init__(self, matrix=None, scale=None, rotation=None,
translation=None):
params = any(param is not None
for param in (scale, rotation, translation))
if params and matrix is not None:
raise ValueError("You cannot specify the transformation matrix and "
"the implicit parameters at the same time.")
elif matrix is not None:
if matrix.shape != (3, 3):
raise ValueError("Invalid shape of transformation matrix.")
self._matrix = matrix
elif params:
if scale is None:
scale = 1
if rotation is None:
rotation = 0
if translation is None:
translation = (0, 0)
self._matrix = np.array([
[math.cos(rotation), - math.sin(rotation), 0],
[math.sin(rotation), math.cos(rotation), 0],
[ 0, 0, 1]
])
self._matrix *= scale
self._matrix[0:2, 2] = translation
else:
# default to an identity transform
self._matrix = np.eye(3)
def estimate(self, src, dst):
xs = src[:, 0]
ys = src[:, 1]
xd = dst[:, 0]
yd = dst[:, 1]
rows = src.shape[0]
# params: a0, a1, b0, b1
A = np.zeros((rows * 2, 5))
A[:rows, 0] = xs
A[:rows, 2] = - ys
A[:rows, 1] = 1
A[rows:, 2] = xs
A[rows:, 0] = ys
A[rows:, 3] = 1
A[:rows, 4] = xd
A[rows:, 4] = yd
_, _, V = np.linalg.svd(A)
# solution is right singular vector that corresponds to smallest
# singular value
a0, a1, b0, b1 = - V[-1, :-1] / V[-1, -1]
self._matrix = np.array([[a0, -b0, a1],
[b0, a0, b1],
[ 0, 0, 1]])
@property
def scale(self):
if math.cos(self.rotation) == 0:
# sin(self.rotation) == 1
scale = self._matrix[0, 1]
else:
scale = self._matrix[0, 0] / math.cos(self.rotation)
return scale
@property
def rotation(self):
return math.atan2(self._matrix[1, 0], self._matrix[1, 1])
@property
def translation(self):
return self._matrix[0:2, 2]
TRANSFORMS = {
'similarity': SimilarityTransform,
}
HOMOGRAPHY_TRANSFORMS = (
SimilarityTransform,
ProjectiveTransform
)
def estimate_transform(ttype, src, dst, **kwargs):
ttype = ttype.lower()
if ttype not in TRANSFORMS:
raise ValueError('the transformation type \'%s\' is not'
'implemented' % ttype)
tform = TRANSFORMS[ttype]()
tform.estimate(src, dst, **kwargs)
return tform
def matrix_transform(coords, matrix):
return ProjectiveTransform(matrix)(coords)
And here's my calculation:
import numpy as np
import transformations as tf #importing above script
##############################
####CALIBRATION POINTS########
##############################
#primary system coordinates obtained instep 2 from surveyed latitude,longitude.
ps1=( 3.50449394074e-07 , 2.28385549832e-06 )
ps2=( -8113.07087572 , -290.673432675 )
ps3=( -13978.9604246 , 7524.00783763 )
ps4=( -14116.6372391 , 11624.2036175 )
ps5=( -3646.95639951 , 14088.88636 )
ps6=( -1362.75920089 , 9755.69391324 )
ps7=( 363.973973779 , 575.837791136 )
ps8=( -1796.13420238 , 1895.18404979 )
ps9=( -2554.36871405 , 1313.79387579 )
ps10=( -5146.3420395 , 1853.8865397 )
ps11=( -10500.2307161 , 6044.01338437 )
ps12=( -9501.32640299 , 12755.170692 )
ps13=( -9312.95200167 , 14605.8597464 )
ps14=( -6149.937535 , 10013.9390815 )
ps15=( -4239.26761866 , 10812.2644791 )
ps16=( -3459.25317782 , 13593.690851 )
ps17=( -3143.43494621 , 12086.0443555 )
ps18=( -9290.50857805 , 15067.4007146 )
ps19=( 817.802325033 , -797.917280294 )
ps20=( 818.077929071 , 508.518662863 )
ps21=( -8281.8734697 , 1486.45713731 )
ps22=( -7082.36334017 , 17973.5167497 )
ps23=( -16042.1577585 , 12030.6541087 )
#secondary system coordinates/known local coordinates
ss1 = ( 41614.22 , 17150.17 )
ss2 = ( 33496.96 , 17024.81 )
ss3 = ( 27791.39 , 24957.15 )
ss4 = ( 27737.21 , 29059.27 )
ss5 = ( 38254.91 , 31310.35 )
ss6 = ( 40450.41 , 26931.58 )
ss7 = ( 41989.871 , 17718.469 )
ss8 = ( 39857.12 , 19081.45 )
ss9 = ( 39087.23 , 18515.701 )
ss10 = ( 36506.74 , 19108.41 )
ss11 = ( 31239.26 , 23406.7 )
ss12 = ( 32374.58 , 30096.13 )
ss13 = ( 32600.628 , 31942.594 )
ss14 = ( 35669.507 , 27287.217 )
ss15 = ( 37596.017 , 28046.484 )
ss16 = ( 38432.47 , 30811.43 )
ss17 = ( 38717.65 , 29297.69 )
ss18 = ( 32632.47 , 32403.62 )
ss19 = ( 42415.52 , 16335.76 )
ss20 = ( 42442.44 , 17641.9 )
ss21 = ( 33364.349 , 18804.941 )
ss22 = ( 34899.27 , 35264.2 )
ss23 = ( 25820.32 , 29504.89 )
############################
############################
############################
from_pt = [ps1, ps2, ps3, ps4, ps5, ps6, ps7, ps8, ps9, ps10, ps11, ps12, ps13, ps14, ps15, ps16, ps17, ps18, ps19, ps20, ps21, ps22, ps23]
to_pt = [ss1, ss2, ss3, ss4, ss5, ss6, ss7, ss8, ss9, ss10, ss11, ss12, ss13, ss14, ss15, ss16, ss17, ss18, ss19, ss20, ss21, ss22, ss23]
fr = np.array(from_pt)
to = np.array(to_pt)
tform = tf.estimate_transform('similarity', fr, to)
#list of coordinates to transform, first converted to sterographic
l = [
[10892.8449025, -11442.6688292],
[10892.2806078, -11442.7145534],
[10880.2426997, -11440.0353547],
[10869.0233644, -11437.2787524],
[10868.4894383, -11437.3154292],
[10879.4637253, -11439.8965462],
[10879.194897, -11440.0837157],
[11661.9477535, -12607.9800965],
[11620.0600416, -12597.7915158],
[11600.121001, -13934.58708]]
for line in l:
x = line[1]
y = line[0]
res= tform(np.array([x,y]))
print res
Final result:
my calculation result known control points coordinates
[[ 30395.74138597 28273.70706175]] # 30395.716 28273.716
[[ 30395.68418212 28273.14381546]] # 30395.659 28273.153
[[ 30398.1177287 28261.05386387]] # 30398.092 28261.063
[[ 30400.64532909 28249.78073875]] # 30400.620 28249.790
[[ 30400.59778907 28249.24767053]] # 30400.572 28249.257
[[ 30398.24064822 28260.27222544]] # 30398.215 28260.281
[[ 30398.04804427 28260.0072639 ]] # 30398.022 28260.016
[[ 29246.33177298 29066.37582526]] # 29246.308 29066.384
[[ 29255.66538993 29024.28939019]] # 29255.641 29024.297
[[ 27918.74210572 29031.57195305]] # 27918.718 29031.581
I think, residuals are acceptable.