text: #include "mapnik_projection.hpp"
  #include "utils.hpp"
  
  #include <mapnik/box2d.hpp>
  
  // boost
  #include MAPNIK_MAKE_SHARED_INCLUDE
  
  Persistent<FunctionTemplate> Projection::constructor;
  
  void Projection::Initialize(Handle<Object> target) {
  
      NanScope();
  
      Local<FunctionTemplate> lcons = NanNew<FunctionTemplate>(Projection::New);
      lcons->InstanceTemplate()->SetInternalFieldCount(1);
      lcons->SetClassName(NanNew("Projection"));
  
      NODE_SET_PROTOTYPE_METHOD(lcons, "forward", forward);
      NODE_SET_PROTOTYPE_METHOD(lcons, "inverse", inverse);
  
      target->Set(NanNew("Projection"), lcons->GetFunction());
      NanAssignPersistent(constructor, lcons);
  }
  
  Projection::Projection(std::string const& name) :
      ObjectWrap(),
      projection_(MAPNIK_MAKE_SHARED<mapnik::projection>(name)) {}
  
  Projection::~Projection()
  {
  }
  
  bool Projection::HasInstance(Handle<Value> val)
  {
      NanScope();
      if (!val->IsObject()) return false;
      Handle<Object> obj = val.As<Object>();
  
      if (NanNew(constructor)->HasInstance(obj))
          return true;
  
      return false;
  }
  
  NAN_METHOD(Projection::New)
  {
      NanScope();
  
      if (!args.IsConstructCall())
      {
          NanThrowError("Cannot call constructor as function, you need to use 'new' keyword");
          NanReturnUndefined();
      }
  
      if (args.Length() <= 0 || !args[0]->IsString()) {
          NanThrowTypeError("please provide a proj4 intialization string");
          NanReturnUndefined();
      }
      try
      {
          Projection* p = new Projection(TOSTR(args[0]));
          p->Wrap(args.This());
          NanReturnValue(args.This());
      }
      catch (std::exception const& ex)
      {
          NanThrowError(ex.what());
          NanReturnUndefined();
      }
  }
  
  NAN_METHOD(Projection::forward)
  {
      NanScope();
      Projection* p = node::ObjectWrap::Unwrap<Projection>(args.Holder());
  
      try
      {
          if (args.Length() != 1) {
              NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
              NanReturnUndefined();
          } else {
              if (!args[0]->IsArray()) {
                  NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
                  NanReturnUndefined();
              }
              Local<Array> a = args[0].As<Array>();
              unsigned int array_length = a->Length();
              if (array_length == 2)
              {
                  double x = a->Get(0)->NumberValue();
                  double y = a->Get(1)->NumberValue();
                  p->projection_->forward(x,y);
                  Local<Array> arr = NanNew<Array>(2);
                  arr->Set(0, NanNew(x));
                  arr->Set(1, NanNew(y));
                  NanReturnValue(arr);
              }
              else if (array_length == 4)
              {
                  double minx = a->Get(0)->NumberValue();
                  double miny = a->Get(1)->NumberValue();
                  double maxx = a->Get(2)->NumberValue();
                  double maxy = a->Get(3)->NumberValue();
                  p->projection_->forward(minx,miny);
                  p->projection_->forward(maxx,maxy);
                  Local<Array> arr = NanNew<Array>(4);
                  arr->Set(0, NanNew(minx));
                  arr->Set(1, NanNew(miny));
                  arr->Set(2, NanNew(maxx));
                  arr->Set(3, NanNew(maxy));
                  NanReturnValue(arr);
              }
              else
              {
                  NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
                  NanReturnUndefined();
              }
          }
      } catch (std::exception const & ex) {
          NanThrowError(ex.what());
          NanReturnUndefined();
      }
  }
  
  NAN_METHOD(Projection::inverse)
  {
      NanScope();
      Projection* p = node::ObjectWrap::Unwrap<Projection>(args.Holder());
  
      try
      {
          if (args.Length() != 1) {
              NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
              NanReturnUndefined();
          } else {
              if (!args[0]->IsArray()) {
                  NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
                  NanReturnUndefined();
              }
              Local<Array> a = args[0].As<Array>();
              unsigned int array_length = a->Length();
              if (array_length == 2)
              {
                  double x = a->Get(0)->NumberValue();
                  double y = a->Get(1)->NumberValue();
                  p->projection_->inverse(x,y);
                  Local<Array> arr = NanNew<Array>(2);
                  arr->Set(0, NanNew(x));
                  arr->Set(1, NanNew(y));
                  NanReturnValue(arr);
              }
              else if (array_length == 4)
              {
                  double minx = a->Get(0)->NumberValue();
                  double miny = a->Get(1)->NumberValue();
                  double maxx = a->Get(2)->NumberValue();
                  double maxy = a->Get(3)->NumberValue();
                  p->projection_->inverse(minx,miny);
                  p->projection_->inverse(maxx,maxy);
                  Local<Array> arr = NanNew<Array>(4);
                  arr->Set(0, NanNew(minx));
                  arr->Set(1, NanNew(miny));
                  arr->Set(2, NanNew(maxx));
                  arr->Set(3, NanNew(maxy));
                  NanReturnValue(arr);
              }
              else
              {
                  NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
                  NanReturnUndefined();
              }
          }
      } catch (std::exception const & ex) {
          NanThrowError(ex.what());
          NanReturnUndefined();
      }
  }
  
  Persistent<FunctionTemplate> ProjTransform::constructor;
  
  void ProjTransform::Initialize(Handle<Object> target) {
  
      NanScope();
  
      Local<FunctionTemplate> lcons = NanNew<FunctionTemplate>(ProjTransform::New);
      lcons->InstanceTemplate()->SetInternalFieldCount(1);
      lcons->SetClassName(NanNew("ProjTransform"));
  
      NODE_SET_PROTOTYPE_METHOD(lcons, "forward", forward);
      NODE_SET_PROTOTYPE_METHOD(lcons, "backward", backward);
  
      target->Set(NanNew("ProjTransform"), lcons->GetFunction());
      NanAssignPersistent(constructor, lcons);
  }
  
  ProjTransform::ProjTransform(mapnik::projection const& src,
                               mapnik::projection const& dest) :
      ObjectWrap(),
      this_(MAPNIK_MAKE_SHARED<mapnik::proj_transform>(src,dest)) {}
  
  ProjTransform::~ProjTransform()
  {
  }
  
  NAN_METHOD(ProjTransform::New)
  {
      NanScope();
      if (!args.IsConstructCall()) {
          NanThrowError("Cannot call constructor as function, you need to use 'new' keyword");
          NanReturnUndefined();
      }
  
      if (args.Length() != 2 || !args[0]->IsObject()  || !args[1]->IsObject()) {
          NanThrowTypeError("please provide two arguments: a pair of mapnik.Projection objects");
          NanReturnUndefined();
      }
  
      Local<Object> src_obj = args[0].As<Object>();
      if (src_obj->IsNull() || src_obj->IsUndefined() || !Projection::HasInstance(src_obj)) {
          NanThrowTypeError("mapnik.Projection expected for first argument");
          NanReturnUndefined();
      }
  
      Local<Object> dest_obj = args[1].As<Object>();
      if (dest_obj->IsNull() || dest_obj->IsUndefined() || !Projection::HasInstance(dest_obj)) {
          NanThrowTypeError("mapnik.Projection expected for second argument");
          NanReturnUndefined();
      }
  
      Projection *p1 = node::ObjectWrap::Unwrap<Projection>(src_obj);
      Projection *p2 = node::ObjectWrap::Unwrap<Projection>(dest_obj);
  
      try
      {
          ProjTransform* p = new ProjTransform(*p1->get(),*p2->get());
          p->Wrap(args.This());
          NanReturnValue(args.This());
      }
      catch (std::exception const& ex)
      {
          NanThrowError(ex.what());
          NanReturnUndefined();
      }
  }
  
  NAN_METHOD(ProjTransform::forward)
  {
      NanScope();
      ProjTransform* p = node::ObjectWrap::Unwrap<ProjTransform>(args.Holder());
  
      if (args.Length() != 1) {
          NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
          NanReturnUndefined();
      } else {
          if (!args[0]->IsArray()) {
              NanThrowTypeError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
              NanReturnUndefined();
          }
  
          Local<Array> a = args[0].As<Array>();
          unsigned int 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();
                  NanThrowError(s.str().c_str());
                  NanReturnUndefined();
  
              }
              Local<Array> arr = NanNew<Array>(2);
              arr->Set(0, NanNew(x));
              arr->Set(1, NanNew(y));
              NanReturnValue(arr);
          }
          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();
                  NanThrowError(s.str().c_str());
                  NanReturnUndefined();
              }
              Local<Array> arr = NanNew<Array>(4);
              arr->Set(0, NanNew(box.minx()));
              arr->Set(1, NanNew(box.miny()));
              arr->Set(2, NanNew(box.maxx()));
              arr->Set(3, NanNew(box.maxy()));
              NanReturnValue(arr);
          }
          else
          {
              NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
              NanReturnUndefined();
          }
      }
  }
  
  NAN_METHOD(ProjTransform::backward)
  {
      NanScope();
      ProjTransform* p = node::ObjectWrap::Unwrap<ProjTransform>(args.Holder());
  
      if (args.Length() != 1) {
          NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
          NanReturnUndefined();
      } else {
          if (!args[0]->IsArray())
          {
              NanThrowTypeError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
              NanReturnUndefined();
          }
  
          Local<Array> a = args[0].As<Array>();
          unsigned int 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();
                  NanThrowError(s.str().c_str());
                  NanReturnUndefined();
              }
              Local<Array> arr = NanNew<Array>(2);
              arr->Set(0, NanNew(x));
              arr->Set(1, NanNew(y));
              NanReturnValue(arr);
          }
          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();
                  NanThrowError(s.str().c_str());
                  NanReturnUndefined();
              }
              Local<Array> arr = NanNew<Array>(4);
              arr->Set(0, NanNew(box.minx()));
              arr->Set(1, NanNew(box.miny()));
              arr->Set(2, NanNew(box.maxx()));
              arr->Set(3, NanNew(box.maxy()));
              NanReturnValue(arr);
          }
          else
          {
              NanThrowError("Must provide an array of either [x,y] or [minx,miny,maxx,maxy]");
              NanReturnUndefined();
          }
      }
  }
  