
Geometry : 
Subject: [geometry] Moving projections from extensions
From: Adam Wulkiewicz (adam.wulkiewicz_at_[hidden])
Date: 20160620 11:59:36
Hi,
I'm planning to move the projections from extensions. I've looked at the
code I have some questions and a proposal of some minor changes to the
interface, to simplify it.
The first question is:
Should the user be able to use the projections directly or should he/she
always use the transform() function with a strategy?
Currently there are several ways of using a projection, e.g.:
// compiletime
projections::parameterspar=projections::init("+ellps=WGS84+units=m");
projections::robin_spheroid<point_ll,point_xy>prj(par);
bool ok = prj.forward(pt_ll,pt_xy);
//runtime
projections::parameterspar=projections::init("+proj=robin+ellps=WGS84+units=m");
// or
projections::parameterspar=projections::init(epsg);
projections::factory<point_ll,point_xy>fac;
boost::shared_ptr<projections::projection<point_ll,point_xy>>prj(fac.create_new(par));
bool ok prj>forward(pt_ll,pt_xy);
// also runtime
projections::project_transformer
<
point_ll_deg,
point_xy
>projection("+proj=robin+ellps=WGS84+units=m"); transform(pt_ll,pt_xy,projection);
There are several problems with this API:
 it is complex
 the implementation details (factory) are exposed to the user
 the parameters are always stored as doubles
 the transform strategy and projections require points as template
parameters
 the transform strategy always use the runtime projection (factory,
dynamic polymorphism, etc.)
 only proj4 strings and EPSG codes can be used to define a projection,
there may be other ways like ESRIflavored WKT
(https://www.nceas.ucsb.edu/scicomp/recipes/projections) or compile time
definition like bg::srs::spheroid
 EPSG codes are simply used to generate proj4 string and then this
string is parsed, I suspect that the EPSG could be used to directly fill
the projection parameters
So in general I propose to hide as much as we can and expose only
crucial parts of the API.
For runtime projections use PIMPL idiom and wrap the pointer inside.
Require only to define CalculationType for projection and use double as
default.
In transform strategy take projection as template parameter and use
runtime projection as default.
// compiletime
projections::robin_spheroid<>prj("+ellps=WGS84+units=m");
projections::robin_spheroid<>prj(srs::spheroid<>());
projections::forward(pt_ll,pt_xy, prj);
// runtime
projections::projection<> prj("+proj=robin+ellps=WGS84+units=m");
projections::projection<> prj(epsg);
projections::forward(pt_ll,pt_xy, prj);
// also runtime
projections::project_transformer<>projection("+proj=robin+ellps=WGS84+units=m");
transform(pt_ll,pt_xy,projection);
If the user shouldn't use the projections directly (only transform +
strategy) we could leave the parameters type (filled in the transform
strategy) as it is now and pass it into the projections instead of proj4
string or epsg code. Then we wouldn't be forced to implement
constructors for all inputs in every projection. But parameters should
also take the CalculationType and I don't see a reason to have init()
function (this also causes problems with multiple representations, see
below):
projections::parameters<> par("+ellps=WGS84+units=m");
projections::parameters<> par(epsg);
projections::robin_spheroid<> prj(par);
Now, regarding the other kinds of projections definitions, both proj4 an
WKTs are strings so currently it wouldn't be possible to implement both
unless there was some other function like init_wkt(), but i think it's
not elegant. Instead I propose wrapping the parameters, like this:
usingnamespaceprojections;
parameters<> par= proj4("+ellps=WGS84+units=m");
parameters<> par = epsg(epsg_code);
parameters<> par = static_epsg<EPSG>();
parameters<> par = spheroid<>();
parameters<> par = wkt("..."); // maybe in the future
// still the user should use:
project_transformer<>projection(proj4("+proj=robin+ellps=WGS84+units=m"));
project_transformer<>projection(epsg(epsg_code));
project_transformer<>projection(spheroid<>()); // compiletime error
project_transformer<aea_ellipsoid<> >projection(spheroid<>()); // ok
transform(pt_ll,pt_xy,projection);
This would make the code more clear too, and semantically separate
Boost.Geometry from the internal implementation being Proj4.
There are also other things I'd like to ask about, i.e.:
What do you think about getting rid of forward and inverse methods and
instead figuring it out from the input types? The projection would
always perform a forward projection if the cartesian geometry was the
second parameter and inverse projection if it was the first parameter.
I'd be similar to boost::lexical_cast which gets the direction from
template parameters. In such case the user would be forced to pass the
correct point/geometries types so this is limiting.
This could be implemented only in the transform strategy (so the
projections would still have forward() and inverse() functions), then
project_inverse_transformer wouldn't be needed.
Another way could be project_transformer automatically figuring out the
projection direction and explicit project_forward_transformer,
project_inverse_transformer strategies.
I'm also confused about the names of projections. AFAIU the ones marked
as xxx_ellipsoid map from ellipsoid of revolution or spheroid and the
ones marked as xxx_spheroid map from a sphere. There is also e.g.
cass_spheroid which AFAIU can work with ellipsoid of revolution. Are
these simple inaccuracies or am I missing something?
Currently the names are directly derived from Proj4 parameter names but
in WKT they're different (PROJECTION["Transverse_Mercator"]) so users
which doesn't know Proj4 could be confused.
What do you think about naming projections using full names, e.g.:
projections::aea_ellipsoid > projections::albers_equal_area
projections::cass_spheroid > projections::cassini
projections::tmerc_ellipsoid > projections::transverse_mercator
This would again be more clear and general. And wouldn't be confusing if
we had triaxial ellipsoid projections in the future.
Regards,
Adam
Geometry list run by mateusz at loskot.net