Permalink
Browse files

reflect mapnik.ProjTransform for robust projection transformations - …

…closes #57
  • Loading branch information...
1 parent 8310ce2 commit 76c7dd75fda0e4e159671712b51a05e263a98a4c Dane Springmeyer committed Apr 17, 2012
@@ -0,0 +1,196 @@
+#include "utils.hpp"
+#include "mapnik_proj_transform.hpp"
+#include "mapnik_projection.hpp"
+
+#include <boost/make_shared.hpp>
+
+Persistent<FunctionTemplate> ProjTransform::constructor;
+
+void ProjTransform::Initialize(Handle<Object> target) {
+
+ HandleScope scope;
+
+ constructor = Persistent<FunctionTemplate>::New(FunctionTemplate::New(ProjTransform::New));
+ constructor->InstanceTemplate()->SetInternalFieldCount(1);
+ constructor->SetClassName(String::NewSymbol("ProjTransform"));
+
+ NODE_SET_PROTOTYPE_METHOD(constructor, "forward", forward);
+ NODE_SET_PROTOTYPE_METHOD(constructor, "backward", backward);
+
+ target->Set(String::NewSymbol("ProjTransform"),constructor->GetFunction());
+}
+
+ProjTransform::ProjTransform(mapnik::projection const& src,
+ mapnik::projection const& dest) :
+ ObjectWrap(),
+ this_(boost::make_shared<mapnik::proj_transform>(src,dest)) {}
+
+ProjTransform::~ProjTransform()
+{
+}
+
+Handle<Value> ProjTransform::New(const Arguments& args)
+{
+ HandleScope scope;
+ if (!args.IsConstructCall())
+ return ThrowException(String::New("Cannot call constructor as function, you need to use 'new' keyword"));
+
+ if (!args.Length() == 2 || !args[0]->IsObject() || !args[1]->IsObject()) {
+ return ThrowException(Exception::TypeError(
+ String::New("please provide two arguments: a pair of mapnik.Projection objects")));
+ }
+
+ Local<Object> src_obj = args[0]->ToObject();
+ if (src_obj->IsNull() || src_obj->IsUndefined() || !Projection::HasInstance(src_obj))
+ return ThrowException(Exception::TypeError(String::New("mapnik.Projection expected for first argument")));
+
+ Local<Object> dest_obj = args[1]->ToObject();
+ if (dest_obj->IsNull() || dest_obj->IsUndefined() || !Projection::HasInstance(dest_obj))
+ return ThrowException(Exception::TypeError(String::New("mapnik.Projection expected for second argument")));
+
+ Projection *p1 = ObjectWrap::Unwrap<Projection>(src_obj);
+ Projection *p2 = ObjectWrap::Unwrap<Projection>(dest_obj);
+
+ try
+ {
+ ProjTransform* p = new ProjTransform(*p1->get(),*p2->get());
+ p->Wrap(args.This());
+ return args.This();
+ }
+ catch (std::exception const& ex)
+ {
+ return ThrowException(Exception::Error(
+ String::New(ex.what())));
+ }
+}
+
+Handle<Value> ProjTransform::forward(const Arguments& args)
+{
+ HandleScope scope;
+ ProjTransform* p = ObjectWrap::Unwrap<ProjTransform>(args.This());
+
+ if (!args.Length() == 1)
+ return ThrowException(Exception::Error(
+ String::New("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]")));
+ else
+ {
+ if (!args[0]->IsArray())
+ return ThrowException(Exception::Error(
+ String::New("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]")));
+
+ Local<Array> a = Local<Array>::Cast(args[0]);
+ uint32_t array_length = a->Length();
+ if (array_length == 2)
+ {
+ double x = a->Get(0)->NumberValue();
+ double y = a->Get(1)->NumberValue();
+ double z = 0;
+ if (!p->this_->forward(x,y,z))
+ {
+ std::ostringstream s;
+ s << "Failed to forward project "
+ << a->Get(0)->NumberValue() << "," << a->Get(1)->NumberValue() << " from " << p->this_->source().params() << " to " << p->this_->dest().params();
+ return ThrowException(Exception::Error(
+ String::New(s.str().c_str())));
+
+ }
+ Local<Array> a = Array::New(2);
+ a->Set(0, Number::New(x));
+ a->Set(1, Number::New(y));
+ return scope.Close(a);
+ }
+ else if (array_length == 4)
+ {
+ mapnik::box2d<double> box(a->Get(0)->NumberValue(),
+ a->Get(1)->NumberValue(),
+ a->Get(2)->NumberValue(),
+ a->Get(3)->NumberValue());
+ if (!p->this_->forward(box))
+ {
+ std::ostringstream s;
+ s << "Failed to forward project "
+ << box << " from " << p->this_->source().params() << " to " << p->this_->dest().params();
+ return ThrowException(Exception::Error(
+ String::New(s.str().c_str())));
+
+ }
+ Local<Array> a = Array::New(4);
+ a->Set(0, Number::New(box.minx()));
+ a->Set(1, Number::New(box.miny()));
+ a->Set(2, Number::New(box.maxx()));
+ a->Set(3, Number::New(box.maxy()));
+ return scope.Close(a);
+ }
+ else
+ return ThrowException(Exception::Error(
+ String::New("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]")));
+
+ }
+}
+
+Handle<Value> ProjTransform::backward(const Arguments& args)
+{
+ HandleScope scope;
+ ProjTransform* p = ObjectWrap::Unwrap<ProjTransform>(args.This());
+
+ if (!args.Length() == 1)
+ return ThrowException(Exception::Error(
+ String::New("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]")));
+ else
+ {
+ if (!args[0]->IsArray())
+ {
+ return ThrowException(Exception::Error(
+ String::New("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]")));
+ }
+
+ Local<Array> a = Local<Array>::Cast(args[0]);
+ uint32_t array_length = a->Length();
+ if (array_length == 2)
+ {
+ double x = a->Get(0)->NumberValue();
+ double y = a->Get(1)->NumberValue();
+ double z = 0;
+ if (!p->this_->backward(x,y,z))
+ {
+ std::ostringstream s;
+ s << "Failed to back project "
+ << a->Get(0)->NumberValue() << "," << a->Get(1)->NumberValue() << " from " << p->this_->dest().params() << " to: " << p->this_->source().params();
+ return ThrowException(Exception::Error(
+ String::New(s.str().c_str())));
+
+ }
+ Local<Array> a = Array::New(2);
+ a->Set(0, Number::New(x));
+ a->Set(1, Number::New(y));
+ return scope.Close(a);
+ }
+ else if (array_length == 4)
+ {
+ mapnik::box2d<double> box(a->Get(0)->NumberValue(),
+ a->Get(1)->NumberValue(),
+ a->Get(2)->NumberValue(),
+ a->Get(3)->NumberValue());
+ if (!p->this_->backward(box))
+ {
+ std::ostringstream s;
+ s << "Failed to back project "
+ << box << " from " << p->this_->source().params() << " to " << p->this_->dest().params();
+ return ThrowException(Exception::Error(
+ String::New(s.str().c_str())));
+
+ }
+ Local<Array> a = Array::New(4);
+ a->Set(0, Number::New(box.minx()));
+ a->Set(1, Number::New(box.miny()));
+ a->Set(2, Number::New(box.maxx()));
+ a->Set(3, Number::New(box.maxy()));
+ return scope.Close(a);
+ }
+ else
+ return ThrowException(Exception::Error(
+ String::New("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]")));
+
+ }
+}
+
@@ -0,0 +1,36 @@
+#ifndef __NODE_MAPNIK_PROJ_TRANSFORM_H__
+#define __NODE_MAPNIK_PROJ_TRANSFORM_H__
+
+#include <v8.h>
+#include <node.h>
+#include <node_object_wrap.h>
+
+// mapnik
+#include <mapnik/proj_transform.hpp>
+
+// boost
+#include <boost/shared_ptr.hpp>
+
+using namespace v8;
+using namespace node;
+
+typedef boost::shared_ptr<mapnik::proj_transform> proj_tr_ptr;
+
+class ProjTransform: public node::ObjectWrap {
+public:
+ static Persistent<FunctionTemplate> constructor;
+ static void Initialize(Handle<Object> target);
+ static Handle<Value> New(const Arguments &args);
+
+ static Handle<Value> forward(const Arguments& args);
+ static Handle<Value> backward(const Arguments& args);
+
+ ProjTransform(mapnik::projection const& src,
+ mapnik::projection const& dest);
+
+private:
+ ~ProjTransform();
+ proj_tr_ptr this_;
+};
+
+#endif
@@ -28,6 +28,17 @@ Projection::~Projection()
{
}
+bool Projection::HasInstance(Handle<Value> val)
+{
+ if (!val->IsObject()) return false;
+ Local<Object> obj = val->ToObject();
+
+ if (constructor->HasInstance(obj))
+ return true;
+
+ return false;
+}
+
Handle<Value> Projection::New(const Arguments& args)
{
HandleScope scope;
@@ -23,9 +23,11 @@ class Projection: public node::ObjectWrap {
static Handle<Value> inverse(const Arguments& args);
static Handle<Value> forward(const Arguments& args);
+ static bool HasInstance(Handle<Value> val);
explicit Projection(std::string const& name);
- explicit Projection(std::string const& name, std::string const& srs);
+
+ inline proj_ptr get() { return projection_; }
private:
~Projection();
View
@@ -15,6 +15,7 @@
#include "mapnik_plugins.hpp"
#include "mapnik_palette.hpp"
#include "mapnik_projection.hpp"
+#include "mapnik_proj_transform.hpp"
#include "mapnik_layer.hpp"
#include "mapnik_datasource.hpp"
#include "mapnik_featureset.hpp"
@@ -97,6 +98,7 @@ extern "C" {
ImageView::Initialize(target);
Palette::Initialize(target);
Projection::Initialize(target);
+ ProjTransform::Initialize(target);
Layer::Initialize(target);
Grid::Initialize(target);
GridView::Initialize(target);
@@ -0,0 +1,92 @@
+var mapnik = require('mapnik');
+var assert = require('assert');
+var fs = require('fs');
+var path = require('path');
+
+describe('mapnik.ProjTransform ', function() {
+ it('should throw with invalid usage', function() {
+ assert.throws(function() { new mapnik.ProjTransform('+init=epsg:foo'); });
+ assert.throws(function() { new mapnik.ProjTransform('+proj +foo'); });
+ assert.throws(function() { new mapnik.ProjTransform(1,1); });
+ //assert.throws(function() { new mapnik.ProjTransform({},{}); });
+ });
+
+ it('should initialize properly', function() {
+ var wgs84 = new mapnik.Projection('+init=epsg:4326');
+ var wgs84_2 = new mapnik.Projection('+init=epsg:4326');
+ var trans = new mapnik.ProjTransform(wgs84,wgs84_2);
+ assert.ok(trans);
+ assert.ok(trans instanceof mapnik.ProjTransform);
+ });
+
+ it('should forward coords properly (no-op)', function() {
+ var wgs84 = new mapnik.Projection('+init=epsg:4326');
+ var wgs84_2 = new mapnik.Projection('+init=epsg:4326');
+ var trans = new mapnik.ProjTransform(wgs84,wgs84_2);
+ var long_lat_coords = [-122.33517, 47.63752];
+ assert.deepEqual(long_lat_coords,trans.forward(long_lat_coords));
+ });
+
+ it('should forward coords properly (no-op)', function() {
+ var wgs84 = new mapnik.Projection('+init=epsg:4326');
+ var wgs84_2 = new mapnik.Projection('+init=epsg:4326');
+ var trans = new mapnik.ProjTransform(wgs84,wgs84_2);
+ var long_lat_box = [-122.33517, 47.63752,-122.33517, 47.63752];
+ assert.deepEqual(long_lat_box,trans.forward(long_lat_box));
+ });
+
+ it('should forward coords properly (4326 -> 3857)', function() {
+ var from = new mapnik.Projection('+init=epsg:4326');
+ var to = new mapnik.Projection('+init=epsg:3857');
+ var trans = new mapnik.ProjTransform(from,to);
+ var long_lat_coords = [-122.33517, 47.63752];
+ var merc = [-13618288.8305, 6046761.54747];
+ assert.notStrictEqual(merc,trans.forward(long_lat_coords));
+ });
+
+ it('should backward coords properly (3857 -> 4326)', function() {
+ var from = new mapnik.Projection('+init=epsg:4326');
+ var to = new mapnik.Projection('+init=epsg:3857');
+ var trans = new mapnik.ProjTransform(from,to);
+ var long_lat_coords = [-122.33517, 47.63752];
+ var merc = [-13618288.8305, 6046761.54747];
+ assert.notStrictEqual(long_lat_coords,trans.backward(merc));
+ });
+
+ it('should throw with invalid coords (4326 -> 3857)', function() {
+ var from = new mapnik.Projection('+init=epsg:4326');
+ var to = new mapnik.Projection('+init=epsg:3857');
+ var trans = new mapnik.ProjTransform(from,to);
+ var long_lat_coords = [-190, 95];
+ assert.throws(function() { trans.forward(long_lat_coords); });
+ });
+
+ it('should forward bbox properly (4326 -> 3857)', function() {
+ var from = new mapnik.Projection('+init=epsg:4326');
+ var to = new mapnik.Projection('+init=epsg:3857');
+ var trans = new mapnik.ProjTransform(from,to);
+ var long_lat_box = [-131.3086, 16.8045, -61.6992, 54.6738];
+ var merc = [-14617205.7910, 1898084.2861, -6868325.6126, 7298818.9559];
+ assert.notStrictEqual(merc,trans.forward(long_lat_box));
+ });
+
+ it('should backward bbox properly (3857 -> 4326)', function() {
+ var from = new mapnik.Projection('+init=epsg:4326');
+ var to = new mapnik.Projection('+init=epsg:3857');
+ var trans = new mapnik.ProjTransform(from,to);
+ var long_lat_box = [-131.3086, 16.8045, -61.6992, 54.6738];
+ var merc = [-14617205.7910, 1898084.2861, -6868325.6126, 7298818.9559];
+ assert.notStrictEqual(long_lat_box,trans.backward(merc));
+ });
+
+ it('should throw with invalid bbox (4326 -> 3857)', function() {
+ var from = new mapnik.Projection('+init=epsg:4326');
+ var to = new mapnik.Projection('+init=epsg:3857');
+ var trans = new mapnik.ProjTransform(from,to);
+ var long_lat_box = [-180,90,180,90];
+ assert.throws(function() { trans.forward(long_lat_box); });
+ });
+
+});
+
+
@@ -9,13 +9,14 @@ describe('mapnik.Projection ', function() {
/failed to initialize projection with: '\+init=epsg:foo'/);
assert.throws(function() { new mapnik.Projection('+proj +foo'); },
/failed to initialize projection with: '\+proj \+foo'/);
+ assert.throws(function() { new mapnik.Projection(1); });
+ assert.throws(function() { new mapnik.Projection({}); });
+ });
-
+ it('should initialize properly', function() {
var wgs84 = new mapnik.Projection('+init=epsg:4326');
assert.ok(wgs84 instanceof mapnik.Projection);
- });
- it('should initialize properly', function() {
var merc;
try {
// perhaps we've got a savvy user?
View
@@ -151,6 +151,7 @@ def build(bld):
"src/mapnik_memory_datasource.cpp",
"src/mapnik_palette.cpp",
"src/mapnik_projection.cpp",
+ "src/mapnik_proj_transform.cpp",
"src/mapnik_layer.cpp",
"src/mapnik_datasource.cpp",
"src/mapnik_featureset.cpp",

0 comments on commit 76c7dd7

Please sign in to comment.