lib/ogr/geometry.rb in ffi-gdal-1.0.0.beta3 vs lib/ogr/geometry.rb in ffi-gdal-1.0.0.beta4

- old
+ new

@@ -1,400 +1,424 @@ require_relative 'envelope' require_relative 'geometry_extensions' +require_relative '../gdal' +require_relative '../gdal/options' +require_relative '../gdal/logger' module OGR - class Geometry - include GDAL::Logger - include GeometryExtensions + module Geometry + module ClassMethods + def create(type) + geometry_pointer = FFI::OGR::API.OGR_G_CreateGeometry(type) + return nil if geometry_pointer.null? + geometry_pointer.autorelease = false - # @param type [FFI::GDAL::OGRwkbGeometryType] - # @return [OGR::Geometry] - def self.create(type) - geometry_pointer = FFI::GDAL.OGR_G_CreateGeometry(type) - return nil if geometry_pointer.null? - geometry_pointer.autorelease = false + factory(geometry_pointer) + end - _to_geometry_type(geometry_pointer) - end + # Creates a new Geometry using the class of the geometry that the type + # represents. + # + # @param geometry [OGR::Geometry, FFI::Pointer] + # @return [OGR::Geometry] + def factory(geometry) + geometry = + if geometry.is_a?(OGR::Geometry) + geometry + else + OGR::UnknownGeometry.new(geometry) + end - def self._to_geometry_type(geometry) - geometry = if geometry.kind_of?(OGR::Geometry) - geometry - else - new(geometry) - end + new_pointer = geometry.c_pointer + # geometry.c_pointer.autorelease = true - case geometry.name - when 'POINT' then OGR::Point.new(geometry.c_pointer) - when 'LINESTRING' then OGR::LineString.new(geometry.c_pointer) - when 'LINEARRING' then OGR::LinearRing.new(geometry.c_pointer) - when 'POLYGON' then OGR::Polygon.new(geometry.c_pointer) - when 'MULTIPOINT' then OGR::MultiPoint.new(geometry.c_pointer) - when 'MULTILINESTRING' then OGR::MultiLineString.new(geometry.c_pointer) - when 'MULTIPOLYGON' then OGR::MultiPolygon.new(geometry.c_pointer) - else - geometry + case geometry.type + when :wkbPoint, :wkbPoint25D then OGR::Point.new(new_pointer) + when :wkbLineString, :wkbLineString25D then OGR::LineString.new(new_pointer) + when :wkbLinearRing then OGR::LinearRing.new(new_pointer) + when :wkbPolygon, :wkbPolygon25D then OGR::Polygon.new(new_pointer) + when :wkbMultiPoint, :wkbMultiPoint25D then OGR::MultiPoint.new(new_pointer) + when :wkbMultiLineString, :wkbMultiLineString25D then OGR::MultiLineString.new(new_pointer) + when :wkbMultiPolygon, :wkbMultiPolygon25D then OGR::MultiPolygon.new(new_pointer) + when :wkbGeometryCollection then OGR::GeometryCollection.new(new_pointer) + when :wkbNone then OGR::NoneGeometry.new(new_pointer) + else + geometry + end end - end - # @param wkt_data [String] - # @param spatial_reference [FFI::Pointer] Optional spatial reference - # to assign to the new geometry. - # @return [OGR::Geometry] - def self.create_from_wkt(wkt_data, spatial_reference=nil) - wkt_data_pointer = FFI::MemoryPointer.from_string(wkt_data) - wkt_pointer_pointer = FFI::MemoryPointer.new(:pointer) - wkt_pointer_pointer.write_pointer(wkt_data_pointer) + # @return [OGR::Geometry] + # @param wkt_data [String] + # @param spatial_ref [FFI::Pointer] Optional spatial reference + # to assign to the new geometry. + # @return [OGR::Geometry] + def create_from_wkt(wkt_data, spatial_ref = nil) + wkt_data_pointer = FFI::MemoryPointer.from_string(wkt_data) + wkt_pointer_pointer = FFI::MemoryPointer.new(:pointer) + wkt_pointer_pointer.write_pointer(wkt_data_pointer) - spatial_ref_pointer = if spatial_reference - GDAL._pointer(OGR::SpatialReference, spatial_reference) - else - FFI::MemoryPointer.new(:pointer) - end + spatial_ref_pointer = + if spatial_ref + GDAL._pointer(OGR::SpatialReference, spatial_ref) + else + nil + end - geometry_ptr = FFI::MemoryPointer.new(:pointer) - geometry_ptr_ptr = FFI::MemoryPointer.new(:pointer) - geometry_ptr_ptr.write_pointer(geometry_ptr) + geometry_ptr = FFI::MemoryPointer.new(:pointer) + geometry_ptr_ptr = FFI::MemoryPointer.new(:pointer) + geometry_ptr_ptr.write_pointer(geometry_ptr) - FFI::GDAL.OGR_G_CreateFromWkt(wkt_pointer_pointer, - spatial_ref_pointer, geometry_ptr_ptr) + FFI::OGR::API.OGR_G_CreateFromWkt(wkt_pointer_pointer, + spatial_ref_pointer, geometry_ptr_ptr) - return nil if geometry_ptr_ptr.null? || - geometry_ptr_ptr.read_pointer.null? + return nil if geometry_ptr_ptr.null? || + geometry_ptr_ptr.read_pointer.null? geometry_ptr_ptr.read_pointer.nil? - # Not assigning here makes tests crash when using a #let. - geometry = _to_geometry_type(geometry_ptr_ptr.read_pointer) - end + geometry = factory(geometry_ptr_ptr.read_pointer) + ObjectSpace.define_finalizer(geometry) { destroy! } - # @param gml_data [String] - # @return [OGR::Geometry] - def self.create_from_gml(gml_data) - geometry_pointer = FFI::GDAL.OGR_G_CreateFromGML(gml_data) + geometry + end - new(geometry_pointer) - end + # @param gml_data [String] + # @return [OGR::Geometry] + def create_from_gml(gml_data) + geometry_pointer = FFI::OGR::API.OGR_G_CreateFromGML(gml_data) - # @param json_data [String] - # @return [OGR::Geometry] - def self.create_from_json(json_data) - geometry_pointer = FFI::GDAL.OGR_G_CreateGeometryFromJson(json_data) + _ = factory(geometry_pointer) + end - new(geometry_pointer) - end + # @param json_data [String] + # @return [OGR::Geometry] + def create_from_json(json_data) + geometry_pointer = FFI::OGR::API.OGR_G_CreateGeometryFromJson(json_data) - # @return [String] - def self.type_to_name(type) - FFI::GDAL.OGRGeometryTypeToName(type) - end + factory(geometry_pointer) + end - # @param geometry [OGR::Geometry, FFI::Pointer] - def initialize(geometry) - @geometry_pointer = GDAL._pointer(OGR::Geometry, geometry) - @geometry_pointer.autorelease = false + # The human-readable string for the geometry type. + # + # @param type [FFI::OGR::WKBGeometryType] + # @return [String] + def type_to_name(type) + FFI::OGR::Core.OGRGeometryTypeToName(type) + end - close_me = -> { - if @geometry_pointer && !@geometry_pointer.null? - FFI::GDAL.OGR_G_DestroyGeometry(@geometry_pointer) - end - } - ObjectSpace.define_finalizer self, close_me + # Finds the most specific common geometry type from the two given types. + # Useful when trying to figure out what geometry type to report for an + # entire layer, when the layer uses multiple types. + # + # @param main [FFI::OGR::WKBGeometryType] + # @param extra [FFI::OGR::WKBGeometryType] + # @return [FFI::OGR::WKBGeometryType] Returns :wkbUnknown when there is + # no type in common. + def merge_geometry_types(main, extra) + FFI::OGR::Core.OGRMergeGeometryTypes(main, extra) + end end - def c_pointer - @geometry_pointer + extend ClassMethods + + def self.included(base) + base.send(:include, GDAL::Logger) + base.send(:include, GeometryExtensions) end - # If this geometry is a container, this adds +geometry+ to the container. - # If this is a Polygon, +geometry+ must be a LinearRing. If the Polygon is - # empty, the first added +geometry+ will be the exterior ring. Subsequent - # geometries added will be interior rings. - # - # @param sub_geometry [OGR::Geometry, FFI::Pointer] - # @return +true+ if successful, otherwise raises an OGR exception. - def add_geometry(sub_geometry) - ogr_err = FFI::GDAL.OGR_G_AddGeometry(@geometry_pointer, pointer_from(sub_geometry)) + #-------------------------------------------------------------------------- + # Instance Methods + #-------------------------------------------------------------------------- - ogr_err.to_ruby - end + # @return [FFI::Pointer] + attr_reader :c_pointer - # @param sub_geometry [OGR::Geometry, FFI::Pointer] - # @return +true+ if successful, otherwise raises an OGR exception. - def add_directly(sub_geometry) - ogr_err = FFI::GDAL.OGR_G_AddGeometryDirectly(@geometry_pointer, pointer_from(sub_geometry)) + # @param value [Boolean] + attr_writer :read_only - ogr_err.to_ruby + def read_only? + @read_only || false end - # @param geometry_index [Fixnum] - # @param delete [Boolean] - # @return +true+ if successful, otherwise raises an OGR exception. - def remove!(geometry_index, delete=true) - ogr_err = FFI::GDAL.OGR_G_RemoveGeometry(@geometry_pointer, geometry_index, delete) + def destroy! + return unless @c_pointer - ogr_err.to_ruby + FFI::OGR::API.OGR_G_DestroyGeometry(@c_pointer) + @c_pointer = nil end # Clears all information from the geometry. + # + # @return nil def empty! - FFI::GDAL.OGR_G_Empty(@geometry_pointer) + FFI::OGR::API.OGR_G_Empty(@c_pointer) end # @return [Fixnum] 0 for points, 1 for lines, 2 for surfaces. def dimension - FFI::GDAL.OGR_G_GetDimension(@geometry_pointer) + FFI::OGR::API.OGR_G_GetDimension(@c_pointer) end # The dimension of coordinates in this geometry (i.e. 2d vs 3d). # # @return [Fixnum] 2 or 3, but 0 in the case of an empty point. def coordinate_dimension - FFI::GDAL.OGR_G_GetCoordinateDimension(@geometry_pointer) + FFI::OGR::API.OGR_G_GetCoordinateDimension(@c_pointer) end # @param new_coordinate_dimension [Fixnum] def coordinate_dimension=(new_coordinate_dimension) unless [2, 3].include?(new_coordinate_dimension) - raise "Can't set coordinate to #{new_coordinate_dimension}. Must be 2 or 3." + fail "Can't set coordinate to #{new_coordinate_dimension}. Must be 2 or 3." end - FFI::GDAL.OGR_G_SetCoordinateDimension(@geometry_pointer, new_coordinate_dimension) + FFI::OGR::API.OGR_G_SetCoordinateDimension(@c_pointer, new_coordinate_dimension) end # @return [OGR::Envelope] def envelope - return @envelope if @envelope - case coordinate_dimension when 2 - envelope = FFI::GDAL::OGREnvelope.new - FFI::GDAL.OGR_G_GetEnvelope(@geometry_pointer, envelope) + envelope = FFI::OGR::Envelope.new + FFI::OGR::API.OGR_G_GetEnvelope(@c_pointer, envelope) when 3 - envelope = FFI::GDAL::OGREnvelope3D.new - FFI::GDAL.OGR_G_GetEnvelope3D(@geometry_pointer, envelope) - when 0 - return nil + envelope = FFI::OGR::Envelope3D.new + FFI::OGR::API.OGR_G_GetEnvelope3D(@c_pointer, envelope) + when 0 then return nil else - raise 'Unknown envelope dimension.' + fail 'Unknown envelope dimension.' end return nil if envelope.null? - @envelope = OGR::Envelope.new(envelope) + OGR::Envelope.new(envelope) end - # @return [FFI::GDAL::OGRwkbGeometryType] + # @return [FFI::OGR::API::WKBGeometryType] def type - FFI::GDAL.OGR_G_GetGeometryType(@geometry_pointer) + FFI::OGR::API.OGR_G_GetGeometryType(@c_pointer) end # @return [String] def type_to_name - FFI::GDAL.OGRGeometryTypeToName(type) + FFI::OGR::Core.OGRGeometryTypeToName(type) end # @return [String] def name - FFI::GDAL.OGR_G_GetGeometryName(@geometry_pointer) + FFI::OGR::API.OGR_G_GetGeometryName(@c_pointer) end # @return [Fixnum] - def count - FFI::GDAL.OGR_G_GetGeometryCount(@geometry_pointer) + def geometry_count + FFI::OGR::API.OGR_G_GetGeometryCount(@c_pointer) end # @return [Fixnum] def point_count return 0 if empty? - FFI::GDAL.OGR_G_GetPointCount(@geometry_pointer) + FFI::OGR::API.OGR_G_GetPointCount(@c_pointer) end # @return [Fixnum] # @todo This regularly crashes, so disabling it. def centroid - raise NotImplementedError, '#centroid not yet implemented.' + fail NotImplementedError, '#centroid not yet implemented.' point = OGR::Geometry.create(:wkbPoint) - FFI::GDAL.OGR_G_Centroid(@geometry_pointer, point.c_pointer) + FFI::OGR::API.OGR_G_Centroid(@c_pointer, point.c_pointer) return nil if point.c_pointer.null? point end # # Dump as WKT to the give +file+. # # @param file [String] The text file to write to. # @param prefix [String] The prefix to put on each line of output. # @return [String] - def dump_readable(file, prefix=nil) - FFI::GDAL.OGR_G_DumpReadable(@geometry_pointer, file, prefix) + def dump_readable(file, prefix = nil) + FFI::OGR::API.OGR_G_DumpReadable(@c_pointer, file, prefix) end # Converts this geometry to a 2D geometry. def flatten_to_2d! - FFI::GDAL.OGR_G_FlattenTo2D(@geometry_pointer) + FFI::OGR::API.OGR_G_FlattenTo2D(@c_pointer) end # @param geometry [OGR::Geometry, FFI::Pointer] # @return [Boolean] def intersects?(geometry) - FFI::GDAL.OGR_G_Intersects(@geometry_pointer, pointer_from(geometry)) + geometry_ptr = GDAL._pointer(OGR::Geometry, geometry) + FFI::OGR::API.OGR_G_Intersects(@c_pointer, geometry_ptr) end # @param geometry [OGR::Geometry, FFI::Pointer] # @return [Boolean] def equals?(geometry) - FFI::GDAL.OGR_G_Equals(@geometry_pointer, pointer_from(geometry)) + return false unless geometry.is_a? OGR::Geometry + + FFI::OGR::API.OGR_G_Equals(@c_pointer, geometry.c_pointer) end alias_method :==, :equals? # @param geometry [OGR::Geometry, FFI::Pointer] # @return [Boolean] def disjoint?(geometry) - FFI::GDAL.OGR_G_Disjoint(@geometry_pointer, pointer_from(geometry)) + geometry_ptr = GDAL._pointer(OGR::Geometry, geometry) + FFI::OGR::API.OGR_G_Disjoint(@c_pointer, geometry_ptr) end # @param geometry [OGR::Geometry, FFI::Pointer] # @return [Boolean] def touches?(geometry) - FFI::GDAL.OGR_G_Touches(@geometry_pointer, geometry.c_pointer) + FFI::OGR::API.OGR_G_Touches(@c_pointer, geometry.c_pointer) end # @param geometry [OGR::Geometry, FFI::Pointer] # @return [Boolean] def crosses?(geometry) - FFI::GDAL.OGR_G_Crosses(@geometry_pointer, pointer_from(geometry)) + geometry_ptr = GDAL._pointer(OGR::Geometry, geometry) + FFI::OGR::API.OGR_G_Crosses(@c_pointer, geometry_ptr) end # @param geometry [OGR::Geometry, FFI::Pointer] # @return [Boolean] def within?(geometry) - FFI::GDAL.OGR_G_Within(@geometry_pointer, pointer_from(geometry)) + geometry_ptr = GDAL._pointer(OGR::Geometry, geometry) + FFI::OGR::API.OGR_G_Within(@c_pointer, geometry_ptr) end # @param geometry [OGR::Geometry, FFI::Pointer] # @return [Boolean] def contains?(geometry) - FFI::GDAL.OGR_G_Contains(@geometry_pointer, pointer_from(geometry)) + geometry_ptr = GDAL._pointer(OGR::Geometry, geometry) + FFI::OGR::API.OGR_G_Contains(@c_pointer, geometry_ptr) end # @param geometry [OGR::Geometry, FFI::Pointer] # @return [Boolean] def overlaps?(geometry) - FFI::GDAL.OGR_G_Overlaps(@geometry_pointer, pointer_from(geometry)) + geometry_ptr = GDAL._pointer(OGR::Geometry, geometry) + FFI::OGR::API.OGR_G_Overlaps(@c_pointer, geometry_ptr) end # @return [Boolean] def empty? - FFI::GDAL.OGR_G_IsEmpty(@geometry_pointer) + FFI::OGR::API.OGR_G_IsEmpty(@c_pointer) end # @return [Boolean] def valid? - FFI::GDAL.OGR_G_IsValid(@geometry_pointer) + FFI::OGR::API.OGR_G_IsValid(@c_pointer) + rescue GDAL::Error + false end # Returns TRUE if the geometry has no anomalous geometric points, such as # self intersection or self tangency. The description of each instantiable # geometric class will include the specific conditions that cause an # instance of that class to be classified as not simple. # # @return [Boolean] def simple? - FFI::GDAL.OGR_G_IsSimple(@geometry_pointer) + FFI::OGR::API.OGR_G_IsSimple(@c_pointer) end # TRUE if the geometry has no points, otherwise FALSE. # # @return [Boolean] def ring? - FFI::GDAL.OGR_G_IsRing(@geometry_pointer) + FFI::OGR::API.OGR_G_IsRing(@c_pointer) + rescue GDAL::Error => ex + if ex.message.include? 'IllegalArgumentException' + false + else + raise + end end # @param other_geometry [OGR::Geometry] # @return [OGR::Geometry] # @todo This regularly crashes, so disabling it. def intersection(other_geometry) - raise NotImplementedError, '#intersection not yet implemented.' + fail NotImplementedError, '#intersection not yet implemented.' return nil unless intersects?(other_geometry) build_geometry do |ptr| - FFI::GDAL.OGR_G_Intersection(ptr, other_geometry.c_pointer) + FFI::OGR::API.OGR_G_Intersection(ptr, other_geometry.c_pointer) end end # @param other_geometry [OGR::Geometry] # @return [OGR::Geometry] def union(other_geometry) build_geometry do |ptr| - FFI::GDAL.OGR_G_Union(ptr, other_geometry.c_pointer) + FFI::OGR::API.OGR_G_Union(ptr, other_geometry.c_pointer) end end # If this or any contained geometries has polygon rings that aren't closed, # this closes them by adding the starting point at the end. def close_rings! - FFI::GDAL.OGR_G_CloseRings(@geometry_pointer) + FFI::OGR::API.OGR_G_CloseRings(@c_pointer) end # Creates a polygon from a set of sparse edges. The newly created geometry # will contain a collection of reassembled Polygons. # # @return [OGR::Geometry] nil if the current geometry isn't a # MultiLineString or if it's impossible to reassemble due to topological # inconsistencies. def polygonize - build_geometry { |ptr| FFI::GDAL.OGR_G_Polygonize(ptr) } + build_geometry { |ptr| FFI::OGR::API.OGR_G_Polygonize(ptr) } end # @param geometry [OGR::Geometry] # @return [OGR::Geometry] def difference(geometry) - new_geometry_ptr = FFI::GDAL.OGR_G_Difference(@geometry_pointer, geometry.c_pointer) + new_geometry_ptr = FFI::OGR::API.OGR_G_Difference(@c_pointer, geometry.c_pointer) return nil if new_geometry_ptr.null? - self.class._to_geometry_type(new_geometry_ptr) + self.class.factory(new_geometry_ptr) end alias_method :-, :difference # @param geometry [OGR::Geometry] # @return [OGR::Geometry] def symmetric_difference(geometry) - new_geometry_ptr = FFI::GDAL.OGR_G_SymDifference(@geometry_pointer, geometry.c_pointer) + new_geometry_ptr = FFI::OGR::API.OGR_G_SymDifference(@c_pointer, geometry.c_pointer) return nil if new_geometry_ptr.null? - self.class._to_geometry_type(new_geometry_ptr) + self.class.factory(new_geometry_ptr) end # The shortest distance between the two geometries. # # @param geometry [OGR::Geometry] # @return [Float] -1 if an error occurs. def distance_to(geometry) - FFI::GDAL.OGR_G_Distance(@geometry_pointer, geometry.c_pointer) + FFI::OGR::API.OGR_G_Distance(@c_pointer, geometry.c_pointer) end # @return [OGR::SpatialReference] def spatial_reference - return @spatial_reference if @spatial_reference - - spatial_ref_ptr = FFI::GDAL.OGR_G_GetSpatialReference(@geometry_pointer) + spatial_ref_ptr = FFI::OGR::API.OGR_G_GetSpatialReference(@c_pointer) return nil if spatial_ref_ptr.null? - @spatial_reference = OGR::SpatialReference.new(spatial_ref_ptr) + OGR::SpatialReference.new(spatial_ref_ptr) end # Assigns a spatial reference to this geometry. Any existing spatial - # reference is replace, but this does not reproject the geometry. + # reference is replaced, but this does not reproject the geometry. # # @param new_spatial_ref [OGR::SpatialReference, FFI::Pointer] def spatial_reference=(new_spatial_ref) new_spatial_ref_ptr = GDAL._pointer(OGR::SpatialReference, new_spatial_ref) - FFI::GDAL.OGR_G_AssignSpatialReference(@geometry_pointer, new_spatial_ref_ptr) + FFI::OGR::API.OGR_G_AssignSpatialReference(@c_pointer, new_spatial_ref_ptr) end # Transforms the coordinates of this geometry in its current spatial # reference system to a new spatial reference system. Normally this means # reprojecting the vectors, but it could also include datum shifts, and @@ -408,15 +432,15 @@ # @return [Boolean] def transform!(coordinate_transformation) coord_trans_ptr = GDAL._pointer(OGR::CoordinateTransformation, coordinate_transformation) - return if coord_trans_ptr.nil? or coord_trans_ptr.null? + return if coord_trans_ptr.nil? || coord_trans_ptr.null? - ogr_err = FFI::GDAL.OGR_G_Transform(@geometry_pointer, coord_trans_ptr) + ogr_err = FFI::OGR::API.OGR_G_Transform(@c_pointer, coord_trans_ptr) - ogr_err.to_ruby + ogr_err.handle_result end # Similar to +#transform+, but this only works if the geometry already has an # assigned spatial reference system _and_ is transformable to the target # coordinate system. @@ -425,194 +449,198 @@ # @return [Boolean] def transform_to!(new_spatial_ref) new_spatial_ref_ptr = GDAL._pointer(OGR::SpatialReference, new_spatial_ref) return nil if new_spatial_ref_ptr.null? - ogr_err = FFI::GDAL.OGR_G_TransformTo(@geometry_pointer, new_spatial_ref_ptr) + ogr_err = FFI::OGR::API.OGR_G_TransformTo(@c_pointer, new_spatial_ref_ptr) - ogr_err.to_ruby + ogr_err.handle_result end # Computes and returns a new, simplified geometry. # - # NOTE: this relies on GDAL having been built against GEOS. If it wasn't, - # this will fail. - # # @param distance_tolerance [Float] + # @param preserve_topology [Boolean] # @return [OGR::Geometry] - def simplify(distance_tolerance) + def simplify(distance_tolerance, preserve_topology: false) build_geometry do |ptr| - FFI::GDAL.OGR_G_Simplify(ptr, distance_tolerance) + if preserve_topology + FFI::OGR::API.OGR_G_SimplifyPreserveTopology(ptr, distance_tolerance) + else + FFI::OGR::API.OGR_G_Simplify(ptr, distance_tolerance) + end end end - # Like +#simplify+, but preserves the geometry's topology. - # - # @param distance_tolerance [Float] - # @return [OGR::Geometry] - def simplify_preserve_topology(distance_tolerance) - build_geometry do |ptr| - FFI::GDAL.OGR_G_SimplifyPreserveTopology(ptr, distance_tolerance) - end - end - # Modify the geometry so that it has no segments longer than +max_length+. # # @param max_length [Float] def segmentize!(max_length) - FFI::GDAL.OGR_G_Segmentize(@geometry_pointer, max_length) + FFI::OGR::API.OGR_G_Segmentize(@c_pointer, max_length) end # @return [OGR::Geometry] def boundary - build_geometry { |ptr| FFI::GDAL.OGR_G_Boundary(ptr) } + build_geometry { |ptr| FFI::OGR::API.OGR_G_Boundary(ptr) } end + # Computes the buffer of the geometry by building a new geometry that + # contains the buffer region around the geometry that this was called on. + # # @param distance [Float] The buffer distance to be applied. # @param quad_segments [Fixnum] The number of segments to use to approximate # a 90 degree (quadrant) of curvature. - # @return [OGR::Geometry] + # @return [OGR::Polygon] def buffer(distance, quad_segments) build_geometry do |ptr| - FFI::GDAL.OGR_G_Buffer(ptr, distance, quad_segments) + FFI::OGR::API.OGR_G_Buffer(ptr, distance, quad_segments) end end # @return [OGR::Geometry] def convex_hull - build_geometry { |ptr| FFI::GDAL.OGR_G_ConvexHull(ptr) } + build_geometry { |ptr| FFI::OGR::API.OGR_G_ConvexHull(ptr) } end - # TODO: should this be a class method? # @param wkb_data [String] Binary WKB data. # @return +true+ if successful, otherwise raises an OGR exception. - def from_wkb(wkb_data) - ogr_err = FFI::GDAL.OGR_G_ImportFromWkb(@geometry_pointer, wkb_data, wkb_data.length) + def import_from_wkb(wkb_data) + ogr_err = FFI::OGR::API.OGR_G_ImportFromWkb(@c_pointer, wkb_data, wkb_data.length) - ogr_err.to_ruby + ogr_err.handle_result end # The exact number of bytes required to hold the WKB of this object. # # @return [Fixnum] def wkb_size - FFI::GDAL.OGR_G_WkbSize(@geometry_pointer) + FFI::OGR::API.OGR_G_WkbSize(@c_pointer) end # @return [String] - def to_wkb(byte_order=:wkbXDR) + def to_wkb(byte_order = :wkbXDR) output = FFI::MemoryPointer.new(:uchar, wkb_size) - ogr_err = FFI::GDAL.OGR_G_ExportToWkb(@geometry_pointer, byte_order, output) - ogr_err.to_ruby + ogr_err = FFI::OGR::API.OGR_G_ExportToWkb(@c_pointer, byte_order, output) + ogr_err.handle_result 'Unable to export geometry to WKB' output.read_bytes(wkb_size) end - # TODO: should this be a class method? # @param wkt_data [String] - def from_wkt(wkt_data) + def import_from_wkt(wkt_data) wkt_data_pointer = FFI::MemoryPointer.from_string(wkt_data) wkt_pointer_pointer = FFI::MemoryPointer.new(:pointer) wkt_pointer_pointer.write_pointer(wkt_data_pointer) - ogr_err = FFI::GDAL.OGR_G_ImportFromWkt(@geometry_pointer, wkt_pointer_pointer) + ogr_err = FFI::OGR::API.OGR_G_ImportFromWkt(@c_pointer, wkt_pointer_pointer) - ogr_err.to_ruby + ogr_err.handle_result "Unable to import: #{wkt_data}" end # @return [String] def to_wkt output = FFI::MemoryPointer.new(:string) - ogr_err = FFI::GDAL.OGR_G_ExportToWkt(@geometry_pointer, output) - ogr_err.to_ruby + ogr_err = FFI::OGR::API.OGR_G_ExportToWkt(@c_pointer, output) + ogr_err.handle_result output.read_pointer.read_string end # This geometry expressed as GML in GML basic data types. # + # @param [Hash] options + # @option options [String] :format "GML3" is really the only "option" here, + # since without passing this in, GDAL defaults to "GML2.1.2" (as of 1.8.0). + # @option options [String] :gml3_linestring_element "curve" is the only + # option here, which only pertains a) to LineString geometries, and b) + # when +:format+ is set to GML3. + # @option options [String] :gml3_longsrs Defaults to "YES", which prefixes + # the EPSG authority with "urn:ogc:def:crs:EPSG::". If "NO", the EPSG + # authority is prefixed with "EPSG:". + # @option options [String] :gmlid Use this to write a gml:id attribute at + # the top level of the geometry. # @return [String] - def to_gml - FFI::GDAL.OGR_G_ExportToGML(@geometry_pointer) + def to_gml(**options) + options_ptr = GDAL::Options.pointer(options) + FFI::OGR::API.OGR_G_ExportToGMLEx(@c_pointer, options_ptr) end # @param altitude_mode [String] Value to write in the +altitudeMode+ # element. # @return [String] - def to_kml(altitude_mode=nil) - FFI::GDAL.OGR_G_ExportToKML(@geometry_pointer, altitude_mode) + def to_kml(altitude_mode = nil) + FFI::OGR::API.OGR_G_ExportToKML(@c_pointer, altitude_mode) end # @return [String] def to_geo_json - FFI::GDAL.OGR_G_ExportToJson(@geometry_pointer) + FFI::OGR::API.OGR_G_ExportToJson(@c_pointer) end - # Converts the current geometry to a Polygon geometry. The returned object - # is a new OGR::Geometry instance. - # - # @return [OGR::Geometry] - def to_polygon - build_geometry { |ptr| FFI::GDAL.OGR_G_ForceToPolygon(ptr) } - end - # Converts the current geometry to a LineString geometry. The returned # object is a new OGR::Geometry instance. # # @return [OGR::Geometry] def to_line_string - build_geometry { |ptr| FFI::GDAL.OGR_G_ForceToLineString(ptr) } + build_geometry { |ptr| FFI::OGR::API.OGR_G_ForceToLineString(ptr) } end - # Converts the current geometry to a MultiPolygon geometry. The returned - # object is a new OGR::Geometry instance. + # Converts the current geometry to a Polygon geometry. The returned object + # is a new OGR::Geometry instance. # # @return [OGR::Geometry] - def to_multi_polygon - build_geometry { |ptr| FFI::GDAL.OGR_G_ForceToMultiPolygon(ptr) } + def to_polygon + build_geometry { |ptr| FFI::OGR::API.OGR_G_ForceToPolygon(ptr) } end # Converts the current geometry to a MultiPoint geometry. The returned # object is a new OGR::Geometry instance. # # @return [OGR::Geometry] def to_multi_point - build_geometry { |ptr| FFI::GDAL.OGR_G_ForceToMultiPoint(ptr) } + build_geometry { |ptr| FFI::OGR::API.OGR_G_ForceToMultiPoint(ptr) } end # Converts the current geometry to a MultiLineString geometry. The returned # object is a new OGR::Geometry instance. # # @return [OGR::Geometry] def to_multi_line_string - build_geometry { |ptr| FFI::GDAL.OGR_G_ForceToMultiLineString(ptr) } + build_geometry { |ptr| FFI::OGR::API.OGR_G_ForceToMultiLineString(ptr) } end + # Converts the current geometry to a MultiPolygon geometry. The returned + # object is a new OGR::Geometry instance. + # + # @return [OGR::MultiPolygon] + def to_multi_polygon + build_geometry { |ptr| FFI::OGR::API.OGR_G_ForceToMultiPolygon(ptr) } + end + private - def build_geometry - geometry_ptr = yield(@geometry_pointer) - return nil if geometry_ptr.null? - - if geometry_ptr == @geometry_pointer - log 'Newly created geometry and current geometry are the same.' - end - - self.class._to_geometry_type(geometry_ptr) + # @param geometry_ptr [OGR::Geometry, FFI::Pointer] + def initialize_from_pointer(geometry_ptr) + fail OGR::InvalidHandle, "Must initialize with a valid pointer: #{geometry_ptr}" if geometry_ptr.nil? + @c_pointer = GDAL._pointer(OGR::Geometry, geometry_ptr) + @read_only = false + @spatial_reference = nil end - def pointer_from(geometry) - if geometry.is_a? OGR::Geometry - geometry.c_pointer - elsif geometry.kind_of? FFI::Pointer - geometry - end - end + def build_geometry + new_geometry_ptr = yield(@c_pointer) + return nil if new_geometry_ptr.nil? || new_geometry_ptr.null? - def object_from(geometry) - if geometry.is_a? OGR::Geometry - geometry - elsif geometry.kind_of? FFI::Pointer - geometry.c_pointer - end + OGR::Geometry.factory(new_geometry_ptr) end end end + +require_relative 'geometries/geometry_collection' +require_relative 'geometries/line_string' +require_relative 'geometries/linear_ring' +require_relative 'geometries/multi_line_string' +require_relative 'geometries/multi_point' +require_relative 'geometries/multi_polygon' +require_relative 'geometries/none_geometry' +require_relative 'geometries/point' +require_relative 'geometries/polygon' +require_relative 'geometries/unknown_geometry'