Hi,

I wanted to see if anyone has opinions on adding ST_Transform in to the 
list of available GIS commands. I've only looked at this from the point of 
view of PostgreSQL and PostGIS, but there is precedent for having Postgre 
only GIS functions. The basic argument is that coordinate transformation is 
a fundamental part of GIS functionality that would be helpful to expose via 
the DAL. In my case, I have users providing coordinates as latitude and 
longitude and want to make distance calculations - we have st_distance but 
it is essentially useless on geographic coordinates.

We can always use `db.executesql` to run more complex GIS commands 
directly, but I don't think it is too hard to make it transformation 
through the DAL. I've got some code I can push but I wanted to see if I'm 
reinventing the wheel or if there is an implementation issue I haven't 
considered.

First, some demo data:

from pydal import geoPoint, geoLine, geoPolygon

# table with two geometry columns, one as WGS84, one as World Equidistant 
Cylindrical
trans_test = db.define_table('transform_test', 
                             Field('point', 'geometry()'), 
                             Field('point_wec', 'geometry(public, 4087, 2)'
))

trans_test.bulk_insert([{'point': geoPoint(0,0)}, {'point': geoPoint(3,0)}])

There are some caveats about the current implementation and the GIS data 
formats that get passed. Record updates work expect a geometry to be 
provided as Well Known Text (WKT) and it seems like the underlying Well 
Known Binary (WKB) representation is always translated to WKT when a 
geometry field is selected. You might expect the output of these to differ 
in the data, but they don't:

db(db.transform_test.id).select(db.transform_test.point).as_list()
#[{'point': 'POINT(0 0)'}, {'point': 'POINT(3 0)'}]

db(db.transform_test).select(db.transform_test.point.st_astext()).as_list()
#[{'_extra': {'ST_AsText("transform_test"."point")': 'POINT(0 0)'}},
# {'_extra': {'ST_AsText("transform_test"."point")': 'POINT(3 0)'}}]

Other functions do return WKB. Compare:

db(db.transform_test).select(db.transform_test.point.st_simplify(0)).as_list
()
#[{'_extra': {'ST_Simplify("transform_test"."point",0.0)': 
'0101000020E610000000000000000000000000000000000000'}},
# {'_extra': {'ST_Simplify("transform_test"."point",0.0)': 
'0101000020E610000000000000000008400000000000000000'}}]

db(db.transform_test).select(db.transform_test.point.st_simplify(0).
st_astext()).as_list()
#[{'_extra': {'ST_AsText(ST_Simplify("transform_test"."point",0.0))': 
'POINT(0 0)'}},
# {'_extra': {'ST_AsText(ST_Simplify("transform_test"."point",0.0))': 
'POINT(3 0)'}}]

If we want to update a record with a simplified geometry, then we have to 
pass in WKT, so have to do this:

simplified = db(db.transform_test.id == 2).select(db.transform_test.point.
st_simplify(0).st_astext().with_alias('simp')).first()
rec =  db.transform_test[2]
rec.update_record(point = simplified['simp'])

# <Row {'point_wec': None, 'id': 2L, 'point': 'POINT(3 0)'}>

But you can do it much more simply using a direct update, because the 
underlying WKB data never leaves the backend.

db(db.transform_test.id == 2).update(point = db.transform_test.point.
st_simplify(0))

I'd suggest that an st_transform function follows the model of st_simplify. 
Given the existing code as model, I have an implementation that seems to 
work:

db(db.transform_test.id).select(db.transform_test.point.st_transform(4087)).
as_list()
#[{'_extra': {'ST_Transform("transform_test"."point",4087)': 
'0101000020F70F000000000000000000000000000000000000'}},
# {'_extra': {'ST_Transform("transform_test"."point",4087)': 
'0101000020F70F00002589B7E3196214410000000000000000'}}]

db(db.transform_test.id).select(db.transform_test.point.st_transform(4087).
st_astext()).as_list()
#[{'_extra': {'ST_AsText(ST_Transform("transform_test"."point",4087))': 
'POINT(0 0)'}},
# {'_extra': {'ST_AsText(ST_Transform("transform_test"."point",4087))': 
'POINT(333958.472379821 0)'}}]

I've chosen the WEC projection because that is easy to verify that a point 
3 degrees east along the equator from the origin has a X cooordinate in WEC 
of 1/120th of the diameter of the Earth given the underlying radius at the 
equator in the WGS84 datum:

import math
(6378137 * 2 * math.pi) / 120
333958.4723798207

We can then do either:

transformed = db(db.transform_test.id == 2).select(db.transform_test.point.
st_transform(4087).st_astext().with_alias('wec')).first()
rec =  db.transform_test[2]
rec.update_record(point_wec = transformed['wec'])

#<Row {'point_wec': 'POINT(333958.472379821 0)', 'id': 2L, 'point': 
'POINT(3 0)'}>

or 

db(db.transform_test.id == 2).update(point_wec = db.transform_test.point.
st_transform(4087))

Thoughts? I haven't made a pull request to pydal but I'm happy to if this 
seems sensible.

Cheers,
David

-- 
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
--- 
You received this message because you are subscribed to the Google Groups 
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to web2py+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to