Skip to content

Commit 84dc192

Browse files
committed
Merge pull request #3 from natmchugh/helmert_transformation
Helmert transformation
2 parents 768f82d + 51e609c commit 84dc192

11 files changed

+582
-0
lines changed

geospatial.c

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
+----------------------------------------------------------------------+
1717
*/
1818

19+
1920
/* $Id$ */
2021

2122
#ifdef HAVE_CONFIG_H
@@ -35,12 +36,57 @@ ZEND_BEGIN_ARG_INFO_EX(haversine_args, 0, 0, 4)
3536
ZEND_ARG_INFO(0, radius)
3637
ZEND_END_ARG_INFO()
3738

39+
ZEND_BEGIN_ARG_INFO_EX(helmert_args, 0, 0, 3)
40+
ZEND_ARG_INFO(0, x)
41+
ZEND_ARG_INFO(0, y)
42+
ZEND_ARG_INFO(0, z)
43+
ZEND_END_ARG_INFO()
44+
45+
ZEND_BEGIN_ARG_INFO_EX(polar_to_cartesian_args, 0, 0, 3)
46+
ZEND_ARG_INFO(0, latitude)
47+
ZEND_ARG_INFO(0, longitude)
48+
ZEND_ARG_INFO(0, reference_ellipsoid)
49+
ZEND_END_ARG_INFO()
50+
51+
ZEND_BEGIN_ARG_INFO_EX(cartesian_to_polar_args, 0, 0, 4)
52+
ZEND_ARG_INFO(0, x)
53+
ZEND_ARG_INFO(0, y)
54+
ZEND_ARG_INFO(0, z)
55+
ZEND_ARG_INFO(0, reference_ellipsoid)
56+
ZEND_END_ARG_INFO()
57+
58+
ZEND_BEGIN_ARG_INFO_EX(transform_datum_args, 0, 0, 4)
59+
ZEND_ARG_INFO(0, latitude)
60+
ZEND_ARG_INFO(0, longitude)
61+
ZEND_ARG_INFO(0, from_reference_ellipsoid)
62+
ZEND_ARG_INFO(0, to_reference_ellipsoid)
63+
ZEND_END_ARG_INFO()
64+
65+
ZEND_BEGIN_ARG_INFO_EX(dms_to_decimal_args, 0, 0, 4)
66+
ZEND_ARG_INFO(0, degrees)
67+
ZEND_ARG_INFO(0, minutes)
68+
ZEND_ARG_INFO(0, seconds)
69+
ZEND_ARG_INFO(0, direction)
70+
ZEND_END_ARG_INFO()
71+
72+
ZEND_BEGIN_ARG_INFO_EX(decimal_to_dms_args, 0, 0, 2)
73+
ZEND_ARG_INFO(0, decimal)
74+
ZEND_ARG_INFO(0, coordinate)
75+
ZEND_END_ARG_INFO()
76+
3877
/* {{{ geospatial_functions[]
3978
*
4079
* Every user visible function must have an entry in geospatial_functions[].
4180
*/
4281
const zend_function_entry geospatial_functions[] = {
4382
PHP_FE(haversine, haversine_args)
83+
PHP_FE(helmert, helmert_args)
84+
PHP_FE(polar_to_cartesian, polar_to_cartesian_args)
85+
PHP_FE(cartesian_to_polar, cartesian_to_polar_args)
86+
PHP_FE(transform_datum, transform_datum_args)
87+
PHP_FE(dms_to_decimal, dms_to_decimal_args)
88+
PHP_FE(decimal_to_dms, decimal_to_dms_args)
89+
/* End of functions */
4490
{ NULL, NULL, NULL }
4591
};
4692
/* }}} */
@@ -75,6 +121,8 @@ PHP_MINIT_FUNCTION(geospatial)
75121
{
76122
REGISTER_DOUBLE_CONSTANT("GEO_DEG_TO_RAD", GEO_DEG_TO_RAD, CONST_CS | CONST_PERSISTENT);
77123
REGISTER_DOUBLE_CONSTANT("GEO_EARTH_RADIUS", GEO_EARTH_RADIUS, CONST_CS | CONST_PERSISTENT);
124+
REGISTER_LONG_CONSTANT("GEO_AIRY_1830", GEO_AIRY_1830, CONST_CS | CONST_PERSISTENT);
125+
REGISTER_LONG_CONSTANT("GEO_WGS84", GEO_WGS84, CONST_CS | CONST_PERSISTENT);
78126
return SUCCESS;
79127
}
80128
/* }}} */
@@ -97,6 +145,252 @@ PHP_MINFO_FUNCTION(geospatial)
97145
}
98146
/* }}} */
99147

148+
geo_ellipsoid get_ellipsoid(long ellipsoid_const)
149+
{
150+
switch (ellipsoid_const) {
151+
case GEO_AIRY_1830:
152+
return airy_1830;
153+
break;
154+
case GEO_WGS84:
155+
default:
156+
return wgs84;
157+
break;
158+
}
159+
}
160+
161+
geo_helmert_constants get_helmert_constants(long from, long to)
162+
{
163+
switch (from - to) {
164+
case 1:
165+
return osgb36_wgs84;
166+
break;
167+
default:
168+
case -1:
169+
return wgs84_osgb36;
170+
break;
171+
}
172+
}
173+
174+
geo_cartesian helmert(double x, double y, double z, geo_helmert_constants helmet_consts)
175+
{
176+
double rX, rY, rZ;
177+
double xOut, yOut, zOut;
178+
double scale_change;
179+
geo_cartesian point;
180+
scale_change = 1 + (helmet_consts.scale_change);
181+
rX = helmet_consts.rotation_x / GEO_SEC_IN_DEG * GEO_DEG_TO_RAD;
182+
rY = helmet_consts.rotation_y / GEO_SEC_IN_DEG * GEO_DEG_TO_RAD;
183+
rZ = helmet_consts.rotation_z / GEO_SEC_IN_DEG * GEO_DEG_TO_RAD;
184+
185+
xOut = helmet_consts.translation_x + ((x - (rZ * y) + (rY * z)) * scale_change);
186+
187+
yOut = helmet_consts.translation_y + (((rZ * x) + y - (rX * z)) * scale_change);
188+
189+
zOut = helmet_consts.translation_z + (((-1 * rY * x) + (rX * y) + z) * scale_change);
190+
191+
point.x = xOut;
192+
point.y = yOut;
193+
point.z = zOut;
194+
return point;
195+
}
196+
197+
geo_cartesian polar_to_cartesian(double latitude, double longitude, geo_ellipsoid eli)
198+
{
199+
double x, y, z;
200+
201+
geo_cartesian point;
202+
double phi = latitude * GEO_DEG_TO_RAD;
203+
double lambda = longitude * GEO_DEG_TO_RAD;
204+
double eSq = ((eli.a * eli.a) - (eli.b * eli.b)) / (eli.a * eli.a);
205+
double nu = eli.a / sqrt(1 - (eSq * sin(phi) * sin(phi)));
206+
x = nu + HEIGHT;
207+
x *= cos(phi) * cos(lambda);
208+
y = nu + HEIGHT;
209+
y *= cos(phi) * sin(lambda);
210+
z = ((1 - eSq) * nu) + HEIGHT;
211+
z*= sin(phi);
212+
point.x = x;
213+
point.y = y;
214+
point.z = z;
215+
return point;
216+
}
217+
218+
219+
geo_lat_long cartesian_to_polar(double x, double y, double z, geo_ellipsoid eli)
220+
{
221+
222+
double latitude, longitude;
223+
double nu, lambda, h;
224+
geo_lat_long polar;
225+
226+
/* aiming for 1m accuracy */
227+
double precision = 0.1 / eli.a;
228+
double eSq = ((eli.a * eli.a) - (eli.b * eli.b)) / (eli.a * eli.a);
229+
double p = sqrt(x * x + y * y);
230+
double phi = atan2(z, p * (1 - eSq));
231+
double phiP = 2 * M_PI;
232+
233+
while (abs(phi - phiP) > precision) {
234+
nu = eli.a / sqrt(1 - eSq * sin(phi) * sin(phi));
235+
phiP = phi;
236+
phi = atan2(z + eSq * nu * sin(phi), p);
237+
}
238+
239+
lambda = atan2(y ,x);
240+
h = p / cos(phi) - nu;
241+
polar.latitude = phi / GEO_DEG_TO_RAD;
242+
polar.longitude = lambda / GEO_DEG_TO_RAD;
243+
polar.height = h;
244+
245+
return polar;
246+
}
247+
248+
/* {{{ proto dms_to_decimal(double degrees, double minutes, double seconds [,string direction])
249+
* Convert degrees, minutes & seconds values to decimal degrees */
250+
PHP_FUNCTION(dms_to_decimal)
251+
{
252+
double degrees, minutes, sign;
253+
double seconds, decimal;
254+
char *direction = "";
255+
int direction_len;
256+
257+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ddd|s", &degrees, &minutes, &seconds, &direction, &direction_len) == FAILURE) {
258+
return;
259+
}
260+
261+
if (strcmp("", direction) == 0) {
262+
sign = degrees > 1 ? 1 : -1;
263+
} else {
264+
sign = strcmp(direction, "S") == 0 || strcmp(direction, "W") == 0 ? -1 : 1;
265+
}
266+
267+
decimal = abs(degrees) + minutes / 60 + seconds / 3600;
268+
decimal *= sign;
269+
RETURN_DOUBLE(decimal);
270+
}
271+
/* }}} */
272+
273+
/* {{{ proto decimal_to_dms(double decimal, string coordinate)
274+
* Convert decimal degrees value to whole degrees and minutes and decimal seconds */
275+
PHP_FUNCTION(decimal_to_dms)
276+
{
277+
double decimal, seconds;
278+
int degrees, minutes;
279+
char *direction;
280+
char *coordinate;
281+
int coordinate_len;
282+
283+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ds", &decimal, &coordinate, &coordinate_len) == FAILURE) {
284+
return;
285+
}
286+
287+
if (strcmp(coordinate, "longitude") == 0) {
288+
direction = decimal < 1 ? "W" : "E";
289+
} else {
290+
direction = decimal < 1 ? "S" : "N";
291+
}
292+
293+
array_init(return_value);
294+
decimal = fabs(decimal);
295+
degrees = (int) decimal;
296+
minutes = decimal * 60 - degrees * 60;
297+
seconds = decimal * 3600 - degrees * 3600 - minutes * 60;
298+
add_assoc_long(return_value, "degrees", degrees);
299+
add_assoc_long(return_value, "minutes", minutes);
300+
add_assoc_double(return_value, "seconds", seconds);
301+
add_assoc_string(return_value, "direction", direction, 1);
302+
}
303+
/* }}} */
304+
305+
/* {{{ proto helmert(double x, double y, double z [, long from_reference_ellipsoid, long to_reference_ellipsoid])
306+
* Convert polar ones (latitude, longitude) tp cartesian co-ordiantes (x, y, z) */
307+
PHP_FUNCTION(helmert)
308+
{
309+
double x, y, z;
310+
geo_cartesian point;
311+
long from_reference_ellipsoid, to_reference_ellipsoid;
312+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ddd|ll", &x, &y, &z, &from_reference_ellipsoid, &to_reference_ellipsoid) == FAILURE) {
313+
return;
314+
}
315+
316+
array_init(return_value);
317+
geo_helmert_constants helmert_constants = get_helmert_constants(from_reference_ellipsoid, to_reference_ellipsoid);
318+
point = helmert(x, y, z, helmert_constants);
319+
add_assoc_double(return_value, "x", point.x);
320+
add_assoc_double(return_value, "y", point.y);
321+
add_assoc_double(return_value, "z", point.z);
322+
}
323+
/* }}} */
324+
325+
/* {{{ proto polar_to_cartesian(double latitude, double longitude[, long reference_ellipsoid])
326+
* Convert polar ones (latitude, longitude) tp cartesian co-ordiantes (x, y, z) */
327+
PHP_FUNCTION(polar_to_cartesian)
328+
{
329+
double latitude, longitude;
330+
long reference_ellipsoid;
331+
geo_cartesian point;
332+
333+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "dd|l", &latitude, &longitude, &reference_ellipsoid) == FAILURE) {
334+
return;
335+
}
336+
337+
geo_ellipsoid eli = get_ellipsoid(reference_ellipsoid);
338+
array_init(return_value);
339+
point = polar_to_cartesian(latitude, longitude, eli);
340+
add_assoc_double(return_value, "x", point.x);
341+
add_assoc_double(return_value, "y", point.y);
342+
add_assoc_double(return_value, "z", point.z);
343+
}
344+
/* }}} */
345+
346+
/* {{{ proto cartesian_to_polar(double x, double y, double z [, long reference_ellipsoid])
347+
* Convert cartesian co-ordiantes (x, y, z) to polar ones (latitude, longitude) */
348+
PHP_FUNCTION(cartesian_to_polar)
349+
{
350+
double x, y, z;
351+
long reference_ellipsoid;
352+
geo_lat_long polar;
353+
354+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ddd|l", &x, &y, &z, &reference_ellipsoid) == FAILURE) {
355+
return;
356+
}
357+
358+
geo_ellipsoid eli = get_ellipsoid(reference_ellipsoid);
359+
array_init(return_value);
360+
polar = cartesian_to_polar(x, y, z, eli);
361+
add_assoc_double(return_value, "lat", polar.latitude);
362+
add_assoc_double(return_value, "long", polar.longitude);
363+
add_assoc_double(return_value, "height", polar.height);
364+
}
365+
/* }}} */
366+
367+
368+
/* {{{ proto transform_datum(double latitude, double longitude, long from_reference_ellipsoid, long to_reference_ellipsoid)
369+
* Unified function to transform projection of geo-cordinates between datums */
370+
PHP_FUNCTION(transform_datum)
371+
{
372+
double latitude, longitude;
373+
long from_reference_ellipsoid, to_reference_ellipsoid;
374+
geo_cartesian point, converted_point;
375+
geo_lat_long polar;
376+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ddll", &latitude, &longitude, &from_reference_ellipsoid, &to_reference_ellipsoid) == FAILURE) {
377+
return;
378+
}
379+
380+
geo_ellipsoid eli_from = get_ellipsoid(from_reference_ellipsoid);
381+
geo_ellipsoid eli_to = get_ellipsoid(to_reference_ellipsoid);
382+
point = polar_to_cartesian(latitude, longitude, eli_from);
383+
geo_helmert_constants helmert_constants = get_helmert_constants(from_reference_ellipsoid, to_reference_ellipsoid);
384+
converted_point = helmert(point.x, point.y, point.z, helmert_constants);
385+
polar = cartesian_to_polar(converted_point.x, converted_point.y, converted_point.z, eli_to);
386+
387+
array_init(return_value);
388+
add_assoc_double(return_value, "lat", polar.latitude);
389+
add_assoc_double(return_value, "long", polar.longitude);
390+
add_assoc_double(return_value, "height", polar.height);
391+
}
392+
/* }}} */
393+
100394
/* {{{ proto haversine(double fromLat, double fromLong, double toLat, double toLong [, double radius ])
101395
* Calculates the greater circle distance between the two lattitude/longitude pairs */
102396
PHP_FUNCTION(haversine)

0 commit comments

Comments
 (0)