Show:
#include "gdal_common.hpp"

#include "gdal_spatial_reference.hpp"
#include "gdal_coordinate_transformation.hpp"
#include "gdal_geometry.hpp"
#include "gdal_geometrycollection.hpp"
#include "gdal_point.hpp"
#include "gdal_linestring.hpp"
#include "gdal_linearring.hpp"
#include "gdal_polygon.hpp"
#include "gdal_multipoint.hpp"
#include "gdal_multilinestring.hpp"
#include "gdal_multipolygon.hpp"

#include <node_buffer.h>
#include <sstream>
#include <stdlib.h>
#include <ogr_core.h>

namespace node_gdal {

Nan::Persistent<FunctionTemplate> Geometry::constructor;

void Geometry::Initialize(Local<Object> target)
{
	Nan::HandleScope scope;

	Local<FunctionTemplate> lcons = Nan::New<FunctionTemplate>(Geometry::New);
	lcons->InstanceTemplate()->SetInternalFieldCount(1);
	lcons->SetClassName(Nan::New("Geometry").ToLocalChecked());

	//Nan::SetMethod(constructor, "fromWKBType", Geometry::create);
	Nan::SetMethod(lcons, "fromWKT", Geometry::createFromWkt);
	Nan::SetMethod(lcons, "fromWKB", Geometry::createFromWkb);
	Nan::SetMethod(lcons, "fromGeoJson", Geometry::createFromGeoJson);
	Nan::SetMethod(lcons, "getName", Geometry::getName);
	Nan::SetMethod(lcons, "getConstructor", Geometry::getConstructor);

	Nan::SetPrototypeMethod(lcons, "toString", toString);
	Nan::SetPrototypeMethod(lcons, "toKML", exportToKML);
	Nan::SetPrototypeMethod(lcons, "toGML", exportToGML);
	Nan::SetPrototypeMethod(lcons, "toJSON", exportToJSON);
	Nan::SetPrototypeMethod(lcons, "toWKT", exportToWKT);
	Nan::SetPrototypeMethod(lcons, "toWKB", exportToWKB);
	Nan::SetPrototypeMethod(lcons, "isEmpty", isEmpty);
	Nan::SetPrototypeMethod(lcons, "isValid", isValid);
	Nan::SetPrototypeMethod(lcons, "isSimple", isSimple);
	Nan::SetPrototypeMethod(lcons, "isRing", isRing);
	Nan::SetPrototypeMethod(lcons, "clone", clone);
	Nan::SetPrototypeMethod(lcons, "empty", empty);
	Nan::SetPrototypeMethod(lcons, "closeRings", closeRings);
	Nan::SetPrototypeMethod(lcons, "intersects", intersects);
	Nan::SetPrototypeMethod(lcons, "equals", equals);
	Nan::SetPrototypeMethod(lcons, "disjoint", disjoint);
	Nan::SetPrototypeMethod(lcons, "touches", touches);
	Nan::SetPrototypeMethod(lcons, "crosses", crosses);
	Nan::SetPrototypeMethod(lcons, "within", within);
	Nan::SetPrototypeMethod(lcons, "contains", contains);
	Nan::SetPrototypeMethod(lcons, "overlaps", overlaps);
	Nan::SetPrototypeMethod(lcons, "boundary", boundary);
	Nan::SetPrototypeMethod(lcons, "distance", distance);
	Nan::SetPrototypeMethod(lcons, "convexHull", convexHull);
	Nan::SetPrototypeMethod(lcons, "buffer", buffer);
	Nan::SetPrototypeMethod(lcons, "intersection", intersection);
	Nan::SetPrototypeMethod(lcons, "union", unionGeometry);
	Nan::SetPrototypeMethod(lcons, "difference", difference);
	Nan::SetPrototypeMethod(lcons, "symDifference", symDifference);
	Nan::SetPrototypeMethod(lcons, "centroid", centroid);
	Nan::SetPrototypeMethod(lcons, "simplify", simplify);
	Nan::SetPrototypeMethod(lcons, "simplifyPreserveTopology", simplifyPreserveTopology);
	Nan::SetPrototypeMethod(lcons, "segmentize", segmentize);
	Nan::SetPrototypeMethod(lcons, "swapXY", swapXY);
	Nan::SetPrototypeMethod(lcons, "getEnvelope", getEnvelope);
	Nan::SetPrototypeMethod(lcons, "getEnvelope3D", getEnvelope3D);
	Nan::SetPrototypeMethod(lcons, "transform", transform);
	Nan::SetPrototypeMethod(lcons, "transformTo", transformTo);

	ATTR(lcons, "srs", srsGetter, srsSetter);
	ATTR(lcons, "wkbSize", wkbSizeGetter, READ_ONLY_SETTER);
	ATTR(lcons, "dimension", dimensionGetter, READ_ONLY_SETTER);
	ATTR(lcons, "coordinateDimension", coordinateDimensionGetter, coordinateDimensionSetter);
	ATTR(lcons, "wkbType", typeGetter, READ_ONLY_SETTER);
	ATTR(lcons, "name", nameGetter, READ_ONLY_SETTER);

	Nan::Set(target, Nan::New("Geometry").ToLocalChecked(), Nan::GetFunction(lcons).ToLocalChecked());

	constructor.Reset(lcons);
}

Geometry::Geometry(OGRGeometry *geom)
	: Nan::ObjectWrap(),
	  this_(geom),
	  owned_(true),
	  size_(0)
{
	LOG("Created Geometry [%p]", geom);
}

Geometry::Geometry()
	: Nan::ObjectWrap(),
	  this_(NULL),
	  owned_(true),
	  size_(0)
{
}

Geometry::~Geometry()
{
	if(this_) {
		LOG("Disposing Geometry [%p] (%s)", this_, owned_ ? "owned" : "unowned");
		if (owned_) {
			OGRGeometryFactory::destroyGeometry(this_);
			Nan::AdjustExternalMemory(-size_);
		}
		LOG("Disposed Geometry [%p]", this_)
		this_ = NULL;
	}
}

/**
 * Abstract base class for all geometry classes.
 *
 * @class gdal.Geometry
 */
NAN_METHOD(Geometry::New)
{
	Nan::HandleScope scope;
	Geometry *f;

	if (!info.IsConstructCall()) {
		Nan::ThrowError("Cannot call constructor as function, you need to use 'new' keyword");
		return;
	}

	if (info[0]->IsExternal()) {
		Local<External> ext = info[0].As<External>();
		void* ptr = ext->Value();
		f = static_cast<Geometry *>(ptr);

	} else {
		Nan::ThrowError("Geometry doesnt have a constructor, use Geometry.fromWKT(), Geometry.fromWKB() or type-specific constructor. ie. new ogr.Point()");
		return;
		//OGRwkbGeometryType geometry_type;
		//NODE_ARG_ENUM(0, "geometry type", OGRwkbGeometryType, geometry_type);
		//OGRGeometry *geom = OGRGeometryFactory::createGeometry(geometry_type);
		//f = new Geometry(geom);
	}

	f->Wrap(info.This());
	info.GetReturnValue().Set(info.This());
}

Local<Value> Geometry::New(OGRGeometry *geom)
{
	Nan::EscapableHandleScope scope;
	return scope.Escape(Geometry::New(geom, true));
}

Local<Value> Geometry::New(OGRGeometry *geom, bool owned)
{
	Nan::EscapableHandleScope scope;

	if (!geom) {
		return scope.Escape(Nan::Null());
	}

	OGRwkbGeometryType type = getGeometryType_fixed(geom);
	type = wkbFlatten(type);

	switch (type) {
		case wkbPoint:
			return scope.Escape(Point::New(static_cast<OGRPoint*>(geom), owned));
		case wkbLineString:
			return scope.Escape(LineString::New(static_cast<OGRLineString*>(geom), owned));
		case wkbLinearRing:
			return scope.Escape(LinearRing::New(static_cast<OGRLinearRing*>(geom), owned));
		case wkbPolygon:
			return scope.Escape(Polygon::New(static_cast<OGRPolygon*>(geom), owned));
		case wkbGeometryCollection:
			return scope.Escape(GeometryCollection::New(static_cast<OGRGeometryCollection*>(geom), owned));
		case wkbMultiPoint:
			return scope.Escape(MultiPoint::New(static_cast<OGRMultiPoint*>(geom), owned));
		case wkbMultiLineString:
			return scope.Escape(MultiLineString::New(static_cast<OGRMultiLineString*>(geom), owned));
		case wkbMultiPolygon:
			return scope.Escape(MultiPolygon::New(static_cast<OGRMultiPolygon*>(geom), owned));
		default:
			Nan::ThrowError("Tried to create unsupported geometry type");
			return scope.Escape(Nan::Undefined());
	}
}

OGRwkbGeometryType Geometry::getGeometryType_fixed(OGRGeometry* geom)
{
	//For some reason OGRLinearRing::getGeometryType uses OGRLineString's method...
	//meaning OGRLinearRing::getGeometryType returns wkbLineString

	//http://trac.osgeo.org/gdal/ticket/1755

	OGRwkbGeometryType type = geom->getGeometryType();

	if (std::string(geom->getGeometryName()) == "LINEARRING") {
		type = (OGRwkbGeometryType) (wkbLinearRing | (type & wkb25DBit));
	}

	return type;
}

NAN_METHOD(Geometry::toString)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	std::ostringstream ss;
	ss << "Geometry (" << geom->this_->getGeometryName() << ")";
	info.GetReturnValue().Set(Nan::New(ss.str().c_str()).ToLocalChecked());
}

/**
 * Closes any un-closed rings.
 *
 * @method closeRings
 */
NODE_WRAPPED_METHOD(Geometry, closeRings, closeRings);

/**
 * Clears the geometry.
 *
 * @method empty
 */
NODE_WRAPPED_METHOD(Geometry, empty, empty);

/**
 * Swaps x, y coordinates.
 *
 * @method swapXY
 */
NODE_WRAPPED_METHOD(Geometry, swapXY, swapXY);

/**
 * Determines if the geometry is empty.
 *
 * @method isEmpty
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT(Geometry, isEmpty, Boolean, IsEmpty);

/**
 * Determines if the geometry is valid.
 *
 * @method isValid
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT(Geometry, isValid, Boolean, IsValid);

/**
 * Determines if the geometry is simple.
 *
 * @method isSimple
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT(Geometry, isSimple, Boolean, IsSimple);

/**
 * Determines if the geometry is a ring.
 *
 * @method isRing
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT(Geometry, isRing, Boolean, IsRing);

/**
 * Determines if the two geometries intersect.
 *
 * @method intersects
 * @param {gdal.Geometry} geometry
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, intersects, Boolean, Intersects, Geometry, "geometry to compare");

/**
 * Determines if the two geometries equal each other.
 *
 * @method equals
 * @param {gdal.Geometry} geometry
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, equals, Boolean, Equals, Geometry, "geometry to compare");

/**
 * Determines if the two geometries are disjoint.
 *
 * @method disjoint
 * @param {gdal.Geometry} geometry
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, disjoint, Boolean, Disjoint, Geometry, "geometry to compare");

/**
 * Determines if the two geometries touch.
 *
 * @method touches
 * @param {gdal.Geometry} geometry
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, touches, Boolean, Touches, Geometry, "geometry to compare");

/**
 * Determines if the two geometries cross.
 *
 * @method crosses
 * @param {gdal.Geometry} geometry
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, crosses, Boolean, Crosses, Geometry, "geometry to compare");

/**
 * Determines if the current geometry is within the provided geometry.
 *
 * @method within
 * @param {gdal.Geometry} geometry
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, within, Boolean, Within, Geometry, "geometry to compare");

/**
 * Determines if the current geometry contains the provided geometry.
 *
 * @method contains
 * @param {gdal.Geometry} geometry
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, contains, Boolean, Contains, Geometry, "geometry to compare");

/**
 * Determines if the current geometry overlaps the provided geometry.
 *
 * @method overlaps
 * @param {gdal.Geometry} geometry
 * @return Boolean
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, overlaps, Boolean, Overlaps, Geometry, "geometry to compare");

/**
 * Computes the distance between the two geometries.
 *
 * @method distance
 * @param {gdal.Geometry} geometry
 * @return Number
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(Geometry, distance, Number, Distance, Geometry, "geometry to use for distance calculation");

/**
 * Modify the geometry such it has no segment longer then the given distance.
 *
 * @method segmentize
 * @param {Number} segment_length
 * @return Number
 */
NODE_WRAPPED_METHOD_WITH_1_DOUBLE_PARAM(Geometry, segmentize, segmentize, "segment length");

/**
 * Apply arbitrary coordinate transformation to the geometry.
 *
 * This method will transform the coordinates of a geometry from their current
 * spatial reference system to a new target spatial reference system. Normally
 * this means reprojecting the vectors, but it could include datum shifts,
 * and changes of units.
 *
 * Note that this method does not require that the geometry already have a
 * spatial reference system. It will be assumed that they can be treated as
 * having the source spatial reference system of the {{#crossLink "gdal.CoordinateTransformation"}}CoordinateTransformation{{/crossLink}}
 * object, and the actual SRS of the geometry will be ignored.
 *
 * @throws Error
 * @method transform
 * @param {gdal.CoordinateTransformation} transformation
 */
NODE_WRAPPED_METHOD_WITH_OGRERR_RESULT_1_WRAPPED_PARAM(Geometry, transform, transform, CoordinateTransformation, "transform");

/**
 * Transforms the geometry to match the provided {{#crossLink "gdal.SpatialReference"}}SpatialReference{{/crossLink}}.
 *
 * @throws Error
 * @method transformTo
 * @param {gdal.SpatialReference} srs
 */
NODE_WRAPPED_METHOD_WITH_OGRERR_RESULT_1_WRAPPED_PARAM(Geometry, transformTo, transformTo, SpatialReference, "spatial reference");

/**
 * Clones the instance.
 *
 * @method clone
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::clone)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(Geometry::New(geom->this_->clone()));
}

/**
 * Compute convex hull.
 *
 * @method convexHull
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::convexHull)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(Geometry::New(geom->this_->ConvexHull()));
}

/**
 * Compute boundary.
 *
 * @method boundary
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::boundary)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(Geometry::New(geom->this_->Boundary()));
}

/**
 * Compute intersection with another geometry.
 *
 * @method intersection
 * @param {gdal.Geometry} geometry
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::intersection)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	Geometry *x = NULL;

	NODE_ARG_WRAPPED(0, "geometry to use for intersection", Geometry, x);

	info.GetReturnValue().Set(Geometry::New(geom->this_->Intersection(x->this_)));
}

/**
 * Compute the union of this geometry with another.
 *
 * @method union
 * @param {gdal.Geometry} geometry
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::unionGeometry)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	Geometry *x = NULL;

	NODE_ARG_WRAPPED(0, "geometry to use for union", Geometry, x);

	info.GetReturnValue().Set(Geometry::New(geom->this_->Union(x->this_)));
}

/**
 * Compute the difference of this geometry with another.
 *
 * @method difference
 * @param {gdal.Geometry} geometry
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::difference)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	Geometry *x = NULL;

	NODE_ARG_WRAPPED(0, "geometry to use for difference", Geometry, x);

	info.GetReturnValue().Set(Geometry::New(geom->this_->Difference(x->this_)));
}

/**
 * Computes the symmetric difference of this geometry and the second geometry.
 *
 * @method symDifference
 * @param {gdal.Geometry} geometry
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::symDifference)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	Geometry *x = NULL;

	NODE_ARG_WRAPPED(0, "geometry to use for symDifference", Geometry, x);

	info.GetReturnValue().Set(Geometry::New(geom->this_->SymDifference(x->this_)));
}

/**
 * Reduces the geometry complexity.
 *
 * @method simplify
 * @param {Number} tolerance
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::simplify)
{
	Nan::HandleScope scope;

	double tolerance;

	NODE_ARG_DOUBLE(0, "tolerance", tolerance);

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	info.GetReturnValue().Set(Geometry::New(geom->this_->Simplify(tolerance)));
}

/**
 * Reduces the geometry complexity while preserving the topology.
 *
 * @method simplifyPreserveTopology
 * @param {Number} tolerance
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::simplifyPreserveTopology)
{
	Nan::HandleScope scope;

	double tolerance;

	NODE_ARG_DOUBLE(0, "tolerance", tolerance);

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	info.GetReturnValue().Set(Geometry::New(geom->this_->SimplifyPreserveTopology(tolerance)));
}

/**
 * Buffers the geometry by the given distance.
 *
 * @method buffer
 * @param {Number} distance
 * @param {integer} segments
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::buffer)
{
	Nan::HandleScope scope;

	double distance;
	int number_of_segments = 30;

	NODE_ARG_DOUBLE(0, "distance", distance);
	NODE_ARG_INT_OPT(1, "number of segments", number_of_segments);

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	info.GetReturnValue().Set(Geometry::New(geom->this_->Buffer(distance, number_of_segments)));
}

/**
 * Convert a geometry into well known text format.
 *
 * @method toWKT
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::exportToWKT)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	char *text = NULL;
	OGRErr err = geom->this_->exportToWkt(&text);

	if(err) {
		NODE_THROW_OGRERR(err);
		return;
	}
	if (text) {
		info.GetReturnValue().Set(SafeString::New(text));
		return;
	}

	return;
}

/**
 * Convert a geometry into well known binary format.
 *
 * @method toWKB
 * @param {string} [byte_order="MSB"] ({{#crossLink "Constants (wkbByteOrder)"}}see options{{/crossLink}})
 * @param {string} [variant="OGC"] ({{#crossLink "Constants (wkbVariant)"}}see options{{/crossLink}})
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::exportToWKB)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	int size = geom->this_->WkbSize();
	unsigned char *data = (unsigned char*) malloc(size);

	//byte order
	OGRwkbByteOrder byte_order;
	std::string order = "MSB";
	NODE_ARG_OPT_STR(0, "byte order", order);
	if (order == "MSB") {
		byte_order = wkbXDR;
	} else if (order == "LSB") {
		byte_order = wkbNDR;
	} else {
		Nan::ThrowError("byte order must be 'MSB' or 'LSB'");
		return;
	}

	#if GDAL_VERSION_MAJOR > 1 || (GDAL_VERSION_MINOR > 10)
	//wkb variant
	OGRwkbVariant wkb_variant;
	std::string variant = "OGC";
	NODE_ARG_OPT_STR(1, "wkb variant", variant);
	if (variant == "OGC") {
		#if GDAL_VERSION_MAJOR > 1
		wkb_variant = wkbVariantOldOgc;
		#else
		wkb_variant = wkbVariantOgc;
		#endif
	} else if (variant == "ISO") {
		wkb_variant = wkbVariantIso;
	} else {
		Nan::ThrowError("variant must be 'OGC' or 'ISO'");
		return;
	}
	OGRErr err = geom->this_->exportToWkb(byte_order, data, wkb_variant);
	#else
	OGRErr err = geom->this_->exportToWkb(byte_order, data);
	#endif

	//^^ export to wkb and fill buffer ^^
	//TODO: avoid extra memcpy in FastBuffer::New and have exportToWkb write directly into buffer

	if(err) {
		free(data);
		NODE_THROW_OGRERR(err);
		return;
	}

	Local<Value> result = Nan::NewBuffer((char *)data, size).ToLocalChecked();

	info.GetReturnValue().Set(result);

}

/**
 * Convert a geometry into KML format.
 *
 * @method toKML
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::exportToKML)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	char *text = geom->this_->exportToKML();
	if (text) {
		Local<Value> result = Nan::New(text).ToLocalChecked();
		CPLFree(text);
		info.GetReturnValue().Set(result);
		return;
	}

	return;
}

/**
 * Convert a geometry into GML format.
 *
 * @method toGML
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::exportToGML)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	char *text = geom->this_->exportToGML();
	if (text) {
		Local<Value> result = Nan::New(text).ToLocalChecked();
		CPLFree(text);
		info.GetReturnValue().Set(result);
		return;
	}

	return;
}

/**
 * Convert a geometry into JSON format.
 *
 * @method toJSON
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::exportToJSON)
{
	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	char *text = geom->this_->exportToJson();
	if (text) {
		Local<Value> result = Nan::New(text).ToLocalChecked();
		CPLFree(text);
		info.GetReturnValue().Set(result);
		return;
	}

	return;
}

/**
 * Compute the centroid of the geometry.
 *
 * @method centroid
 * @return gdal.Point
 */
NAN_METHOD(Geometry::centroid)
{
	// The Centroid method wants the caller to create the point to fill in. Instead
	// of requiring the caller to create the point geometry to fill in, we new up an
	// OGRPoint and put the result into it and return that.
	Nan::HandleScope scope;
	OGRPoint *point = new OGRPoint();

	Nan::ObjectWrap::Unwrap<Geometry>(info.This())->this_->Centroid(point);

	info.GetReturnValue().Set(Point::New(point));
}

/**
 * Computes the bounding box (envelope).
 *
 * @method getEnvelope
 * @return {gdal.Envelope} Bounding envelope
 */
NAN_METHOD(Geometry::getEnvelope)
{
	//returns object containing boundaries until complete OGREnvelope binding is built

	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	OGREnvelope *envelope = new OGREnvelope();
	geom->this_->getEnvelope(envelope);

	Local<Object> obj = Nan::New<Object>();
	Nan::Set(obj, Nan::New("minX").ToLocalChecked(), Nan::New<Number>(envelope->MinX));
	Nan::Set(obj, Nan::New("maxX").ToLocalChecked(), Nan::New<Number>(envelope->MaxX));
	Nan::Set(obj, Nan::New("minY").ToLocalChecked(), Nan::New<Number>(envelope->MinY));
	Nan::Set(obj, Nan::New("maxY").ToLocalChecked(), Nan::New<Number>(envelope->MaxY));

	delete envelope;

	info.GetReturnValue().Set(obj);
}

/**
 * Computes the 3D bounding box (envelope).
 *
 * @method getEnvelope3D
 * @return {gdal.Envelope3D} Bounding envelope
 */
NAN_METHOD(Geometry::getEnvelope3D)
{
	//returns object containing boundaries until complete OGREnvelope binding is built

	Nan::HandleScope scope;

	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	OGREnvelope3D *envelope = new OGREnvelope3D();
	geom->this_->getEnvelope(envelope);

	Local<Object> obj = Nan::New<Object>();
	Nan::Set(obj, Nan::New("minX").ToLocalChecked(), Nan::New<Number>(envelope->MinX));
	Nan::Set(obj, Nan::New("maxX").ToLocalChecked(), Nan::New<Number>(envelope->MaxX));
	Nan::Set(obj, Nan::New("minY").ToLocalChecked(), Nan::New<Number>(envelope->MinY));
	Nan::Set(obj, Nan::New("maxY").ToLocalChecked(), Nan::New<Number>(envelope->MaxY));
	Nan::Set(obj, Nan::New("minZ").ToLocalChecked(), Nan::New<Number>(envelope->MinZ));
	Nan::Set(obj, Nan::New("maxZ").ToLocalChecked(), Nan::New<Number>(envelope->MaxZ));

	delete envelope;

	info.GetReturnValue().Set(obj);
}

// --- JS static methods (OGRGeometryFactory) ---

/**
 * Creates a Geometry from a WKT string.
 *
 * @static
 * @method fromWKT
 * @param {String} wkt
 * @param {gdal.SpatialReference} [srs]
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::createFromWkt)
{
	Nan::HandleScope scope;

	std::string wkt_string;
	SpatialReference *srs = NULL;

	NODE_ARG_STR(0, "wkt", wkt_string);
	NODE_ARG_WRAPPED_OPT(1, "srs", SpatialReference, srs);

	char *wkt = (char*) wkt_string.c_str();
	OGRGeometry *geom = NULL;
	OGRSpatialReference *ogr_srs = NULL;
	if (srs) {
		ogr_srs = srs->get();
	}

	OGRErr err = OGRGeometryFactory::createFromWkt(&wkt, ogr_srs, &geom);
	if (err) {
		NODE_THROW_OGRERR(err);
		return;
	}

	info.GetReturnValue().Set(Geometry::New(geom, true));
}

/**
 * Creates a Geometry from a WKB buffer.
 *
 * @static
 * @method fromWKB
 * @param {Buffer} wkb
 * @param {gdal.SpatialReference} [srs]
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::createFromWkb)
{
	Nan::HandleScope scope;

	std::string wkb_string;
	SpatialReference *srs = NULL;

	Local<Object> wkb_obj;
	NODE_ARG_OBJECT(0, "wkb", wkb_obj);
	NODE_ARG_WRAPPED_OPT(1, "srs", SpatialReference, srs);

	std::string obj_type = *Nan::Utf8String(wkb_obj->GetConstructorName());

	if(obj_type != "Buffer" && obj_type != "Uint8Array"){
		Nan::ThrowError("Argument must be a buffer object");
		return;
	}

	unsigned char* data = (unsigned char *) Buffer::Data(wkb_obj);
	size_t length = Buffer::Length(wkb_obj);

	OGRGeometry *geom = NULL;
	OGRSpatialReference *ogr_srs = NULL;
	if (srs) {
		ogr_srs = srs->get();
	}

	OGRErr err = OGRGeometryFactory::createFromWkb(data, ogr_srs, &geom, length);
	if (err) {
		NODE_THROW_OGRERR(err);
		return;
	}

	info.GetReturnValue().Set(Geometry::New(geom, true));
}


/**
 * Creates a Geometry from a GeoJSON string. Requires GDAL>=2.3.
 *
 * @static
 * @method fromGeoJson
 * @param {Object} geojson
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::createFromGeoJson) {
	Nan::HandleScope scope;
#if GDAL_VERSION_MAJOR < 2 || (GDAL_VERSION_MAJOR <= 2 && GDAL_VERSION_MINOR < 3)
	Nan::ThrowError("GDAL < 2.3 does not support parsing GeoJSON directly");
	return;
#else
	if (info.Length() < 1) {
		Nan::ThrowError("Missing required argument");
		return;
	}
	Local<Value> input = info[0].As<Value>();

	std::string val;
	if (input->IsString()) {
		val = *Nan::Utf8String(input);
	} else if (input->IsObject() && !input->IsNull()) {
		// goes to text to pass it in, there isn't a performant way to
		// go from v8 JSON -> CPLJSON anyways
		Nan::JSON NanJSON;
		v8::Local<v8::Object> inputObject = v8::Local<v8::Object>::Cast(input);
		Nan::MaybeLocal<String> result = NanJSON.Stringify(inputObject);
		if (result.IsEmpty()) {
			Nan::ThrowError("Invalid GeoJSON");
			return;
		}
		Local<String> stringified = result.ToLocalChecked();
		val = *Nan::Utf8String(stringified);
	} else {
		Nan::ThrowError("Invalid GeoJSON (must a GeoJSON object or serialized string)");
		return;
	}

	OGRGeometry *geom = OGRGeometryFactory::createFromGeoJson(val.c_str());
	info.GetReturnValue().Set(Geometry::New(geom, true));
#endif
}

/**
 * Creates an empty Geometry from a WKB type.
 *
 * @static
 * @method create
 * @param {Integer} type WKB geometry type ({{#crossLink "Constants (wkbGeometryType)"}}available options{{/crossLink}})
 * @return gdal.Geometry
 */
NAN_METHOD(Geometry::create)
{
	Nan::HandleScope scope;

	OGRwkbGeometryType type = wkbUnknown;
	NODE_ARG_ENUM(0, "type", OGRwkbGeometryType, type);

	info.GetReturnValue().Set(Geometry::New(OGRGeometryFactory::createGeometry(type), true));
}

/**
 * @attribute srs
 * @type gdal.SpatialReference
 */
NAN_GETTER(Geometry::srsGetter)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(SpatialReference::New(geom->this_->getSpatialReference(), false));
}

NAN_SETTER(Geometry::srsSetter)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	OGRSpatialReference *srs = NULL;
	if (IS_WRAPPED(value, SpatialReference)) {
		SpatialReference *srs_obj = Nan::ObjectWrap::Unwrap<SpatialReference>(value.As<Object>());
		srs = srs_obj->get();
	} else if (!value->IsNull() && !value->IsUndefined()) {
		Nan::ThrowError("srs must be SpatialReference object");
		return;
	}

	geom->this_->assignSpatialReference(srs);
}

/**
 * @readOnly
 * @attribute name
 * @type String
 */
NAN_GETTER(Geometry::nameGetter)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(SafeString::New(geom->this_->getGeometryName()));
}

/**
 * See {{#crossLink "Constants (wkbGeometryType)"}}wkbGeometryTypes{{/crossLink}}.
 * @readOnly
 * @attribute wkbType
 * @type integer
 */
NAN_GETTER(Geometry::typeGetter)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(Nan::New<Integer>(getGeometryType_fixed(geom->this_)));
}

/**
 * @readOnly
 * @attribute wkbSize
 * @type Integer
 */
NAN_GETTER(Geometry::wkbSizeGetter)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(Nan::New<Integer>(geom->this_->WkbSize()));
}

/**
 * @readOnly
 * @attribute dimension
 * @type Integer
 */
NAN_GETTER(Geometry::dimensionGetter)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(Nan::New<Integer>(geom->this_->getDimension()));
}

/**
 * @attribute coordinateDimension
 * @type Integer
 */
NAN_GETTER(Geometry::coordinateDimensionGetter)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());
	info.GetReturnValue().Set(Nan::New<Integer>(geom->this_->getCoordinateDimension()));
}

NAN_SETTER(Geometry::coordinateDimensionSetter)
{
	Nan::HandleScope scope;
	Geometry *geom = Nan::ObjectWrap::Unwrap<Geometry>(info.This());

	if (!value->IsInt32()) {
		Nan::ThrowError("coordinateDimension must be an integer");
		return;
	}
	int dim = Nan::To<int64_t>(value).ToChecked();
	if (dim != 2 && dim != 3) {
		Nan::ThrowError("coordinateDimension must be 2 or 3");
		return;
	}

	geom->this_->setCoordinateDimension(dim);
}


Local<Value> Geometry::getConstructor(OGRwkbGeometryType type){
	Nan::EscapableHandleScope scope;

	type = wkbFlatten(type);
	switch (type) {
		case wkbPoint:              return scope.Escape(Nan::GetFunction(Nan::New(Point::constructor)).ToLocalChecked());
		case wkbLineString:         return scope.Escape(Nan::GetFunction(Nan::New(LineString::constructor)).ToLocalChecked());
		case wkbLinearRing:         return scope.Escape(Nan::GetFunction(Nan::New(LinearRing::constructor)).ToLocalChecked());
		case wkbPolygon:            return scope.Escape(Nan::GetFunction(Nan::New(Polygon::constructor)).ToLocalChecked());
		case wkbGeometryCollection: return scope.Escape(Nan::GetFunction(Nan::New(GeometryCollection::constructor)).ToLocalChecked());
		case wkbMultiPoint:         return scope.Escape(Nan::GetFunction(Nan::New(MultiPoint::constructor)).ToLocalChecked());
		case wkbMultiLineString:    return scope.Escape(Nan::GetFunction(Nan::New(MultiLineString::constructor)).ToLocalChecked());
		case wkbMultiPolygon:       return scope.Escape(Nan::GetFunction(Nan::New(MultiPolygon::constructor)).ToLocalChecked());
		default:                    return scope.Escape(Nan::Null());
	}
}

/**
 * Returns the Geometry subclass that matches the
 * given WKB geometry type.
 *
 * @static
 * @method getConstructor
 * @param {Integer} type WKB geometry type ({{#crossLink "Constants (wkbGeometryType)"}}available options{{/crossLink}})
 * @return Function
 */
NAN_METHOD(Geometry::getConstructor)
{
	Nan::HandleScope scope;
	OGRwkbGeometryType type;
	NODE_ARG_ENUM(0, "wkbType", OGRwkbGeometryType, type);
	info.GetReturnValue().Set(getConstructor(type));
}

/**
 * Returns the Geometry subclass name that matches the
 * given WKB geometry type.
 *
 * @static
 * @method getName
 * @param {Integer} type WKB geometry type ({{#crossLink "Constants (wkbGeometryType)"}}available options{{/crossLink}})
 * @return String
 */
NAN_METHOD(Geometry::getName)
{
	Nan::HandleScope scope;
	OGRwkbGeometryType type;
	NODE_ARG_ENUM(0, "wkbType", OGRwkbGeometryType, type);
	info.GetReturnValue().Set(SafeString::New(OGRGeometryTypeToName(type)));
}

} // namespace node_gdal