<commit_before>module OGR
  class DataSource
    include FFIOGR

    attr_accessor :ptr

    def initialize(ptr)
      @ptr = FFI::AutoPointer.new(ptr, self.class.method(:release))
      @ptr.autorelease = false
    end

    def self.release(ptr);end

    def free
      FFIOGR.OGR_DS_Destroy(@ptr)
    end

    def copy(output_type, output_path, driver_options=nil)
      driver = OGRGetDriverByName(OGR::DRIVER_TYPES[output_type.downcase])
      new_ds = FFIOGR.OGR_Dr_CopyDataSource(driver, @ptr, File.expand_path(output_path), driver_options)
      FFIOGR.OGR_DS_Destroy(new_ds)
    end

    def copy_with_transform(output_type, output_path, spatial_ref=nil, driver_options=nil)
      writer = OGR::Writer.new(OGR::DRIVER_TYPES[output_type.downcase])
      writer.set_output(output_path)
      out = writer.ptr

      layers.each do |layer|
        name = layer.name
        geometry_type = layer.geometry_type
        old_sr = layer.spatial_ref

        ct = OGR::CoordinateTransformation.find_transformation(old_sr, spatial_ref) unless spatial_ref.nil? || (spatial_ref == old_sr)

        sr = spatial_ref.nil? ? nil : spatial_ref.ptr

        new_layer = out.add_layer name, geometry_type, sr, driver_options

        ptr = layer.ptr

        layer_definition = FFIOGR.OGR_L_GetLayerDefn(ptr)
        field_count = FFIOGR.OGR_FD_GetFieldCount(layer_definition)

        for i in (0...field_count)
          fd = FFIOGR.OGR_FD_GetFieldDefn(layer_definition, i)
          name = FFIOGR.OGR_Fld_GetNameRef(fd)
          type = FFIOGR.OGR_Fld_GetType(fd)

          opts = {}

          opts[:precision] = FFIOGR.OGR_Fld_GetPrecision(fd) if type == :real
          opts[:width] = FFIOGR.OGR_Fld_GetWidth(fd) if type == :string

          new_layer.add_field name, type
        end

        layer.features.each do |feature|
          geometry = OGR::Tools.cast_geometry(feature.geometry)
          geometry.transform ct if ct

          new_feature = new_layer.create_feature
          new_feature.add_geometry geometry

          ptr = feature.ptr
          field_count = FFIOGR.OGR_F_GetFieldCount(ptr)

          for i in (0...field_count)
            fd = FFIOGR.OGR_F_GetFieldDefnRef(ptr, i)
            field_name = FFIOGR.OGR_Fld_GetNameRef(fd)
            field_type = FFIOGR.OGR_Fld_GetType(fd)

            case field_type
            when :integer
              field_value = FFIOGR.OGR_F_GetFieldAsInteger(ptr, i)
            when :real
              field_value = FFIOGR.OGR_F_GetFieldAsDouble(ptr, i)
            else
              field_value = FFIOGR.OGR_F_GetFieldAsString(ptr, i)
            end

            new_feature.set_field_value field_name, field_value, field_type
          end

          new_layer.add_feature new_feature
        end

        new_layer.sync
      end
      out.free
    end

    def add_layer(name, geometry_type, spatial_ref=nil, options=nil)
      layer = FFIOGR.OGR_DS_CreateLayer(@ptr, name, spatial_ref, geometry_type.to_sym, options)
      OGR::Tools.cast_layer(layer)
    end

    def num_layers
      FFIOGR.OGR_DS_GetLayerCount(@ptr)
    end

    def get_layers
      layers = []

      for i in (0...num_layers) do
        layers << OGR::Tools.cast_layer(OGR_DS_GetLayer(@ptr, i))
      end

      layers
    end
    alias_method :layers, :get_layers

    def get_features
      layers.map {|l| l.features}
    end
    alias_method :features, :get_features

    def get_geometries(as_geojson=false)
      unless as_geojson
        features.map {|feature| feature.map {|f| OGR::Tools.cast_geometry(f.geometry)}}
      else
        features.map {|feature| feature.map {|f| OGR::Tools.cast_geometry(f.geometry).to_geojson}}
      end
    end
    alias_method :geometries, :get_geometries

    def get_fields
      features.map {|feature| feature.map {|f| f.fields}}
    end
    alias_method :fields, :get_fields

    def to_format(format, output_path, options={}, driver_options=nil)
      raise RuntimeError.new("Output path not specified.") if output_path.nil?

      spatial_ref = options[:spatial_ref]

      unless spatial_ref
        copy format, output_path, driver_options
      else
        if spatial_ref.instance_of? OGR::SpatialReference
          copy_with_transform format, output_path, spatial_ref, driver_options
        else
          raise RuntimeError.new("Invalid spatial reference specified.")
        end
      end
    end

    def to_shp(output_path, options={})
      to_format('shapefile', output_path, options)
    end

    def to_csv(output_path, options={})
      to_format('csv', output_path, options)
    end

    def to_kml(output_path, options={})
      warn "KML output will always be in EPSG:4326" unless options[:spatial_ref].nil?
      to_format('kml', output_path, options)
    end

    def to_geojson(output_path, options={})
      driver_options = nil

      if options[:bbox]
        # this segfaults -- working on solution
        bbox = FFI::MemoryPointer.from_string "WRITE_BBOX=YES"
        driver_options = FFI::MemoryPointer.new :pointer, 1
        driver_options[0].put_pointer 0, bbox
      end

      to_format('geojson', output_path, options, driver_options)
    end

    def parse_driver_options(options, driver_options=nil)
      # not implemented
      driver_options
    end<commit_msg>Parse driver options from options hash in to_* methods<commit_after>module OGR
  class DataSource
    include FFIOGR

    attr_accessor :ptr

    def initialize(ptr)
      @ptr = FFI::AutoPointer.new(ptr, self.class.method(:release))
      @ptr.autorelease = false
    end

    def self.release(ptr);end

    def free
      FFIOGR.OGR_DS_Destroy(@ptr)
    end

    def copy(output_type, output_path, driver_options=nil)
      driver = OGRGetDriverByName(OGR::DRIVER_TYPES[output_type.downcase])
      new_ds = FFIOGR.OGR_Dr_CopyDataSource(driver, @ptr, File.expand_path(output_path), driver_options)
      FFIOGR.OGR_DS_Destroy(new_ds)
    end

    def copy_with_transform(output_type, output_path, spatial_ref=nil, driver_options=nil)
      writer = OGR::Writer.new(OGR::DRIVER_TYPES[output_type.downcase])
      writer.set_output(output_path)
      out = writer.ptr

      layers.each do |layer|
        name = layer.name
        geometry_type = layer.geometry_type
        old_sr = layer.spatial_ref

        ct = OGR::CoordinateTransformation.find_transformation(old_sr, spatial_ref) unless spatial_ref.nil? || (spatial_ref == old_sr)

        sr = spatial_ref.nil? ? nil : spatial_ref.ptr

        new_layer = out.add_layer name, geometry_type, sr, driver_options

        ptr = layer.ptr

        layer_definition = FFIOGR.OGR_L_GetLayerDefn(ptr)
        field_count = FFIOGR.OGR_FD_GetFieldCount(layer_definition)

        for i in (0...field_count)
          fd = FFIOGR.OGR_FD_GetFieldDefn(layer_definition, i)
          name = FFIOGR.OGR_Fld_GetNameRef(fd)
          type = FFIOGR.OGR_Fld_GetType(fd)

          opts = {}

          opts[:precision] = FFIOGR.OGR_Fld_GetPrecision(fd) if type == :real
          opts[:width] = FFIOGR.OGR_Fld_GetWidth(fd) if type == :string

          new_layer.add_field name, type
        end

        layer.features.each do |feature|
          geometry = OGR::Tools.cast_geometry(feature.geometry)
          geometry.transform ct if ct

          new_feature = new_layer.create_feature
          new_feature.add_geometry geometry

          ptr = feature.ptr
          field_count = FFIOGR.OGR_F_GetFieldCount(ptr)

          for i in (0...field_count)
            fd = FFIOGR.OGR_F_GetFieldDefnRef(ptr, i)
            field_name = FFIOGR.OGR_Fld_GetNameRef(fd)
            field_type = FFIOGR.OGR_Fld_GetType(fd)

            case field_type
            when :integer
              field_value = FFIOGR.OGR_F_GetFieldAsInteger(ptr, i)
            when :real
              field_value = FFIOGR.OGR_F_GetFieldAsDouble(ptr, i)
            else
              field_value = FFIOGR.OGR_F_GetFieldAsString(ptr, i)
            end

            new_feature.set_field_value field_name, field_value, field_type
          end

          new_layer.add_feature new_feature
        end

        new_layer.sync
      end
      out.free
    end

    def add_layer(name, geometry_type, spatial_ref=nil, options=nil)
      layer = FFIOGR.OGR_DS_CreateLayer(@ptr, name, spatial_ref, geometry_type.to_sym, options)
      OGR::Tools.cast_layer(layer)
    end

    def num_layers
      FFIOGR.OGR_DS_GetLayerCount(@ptr)
    end

    def get_layers
      layers = []

      for i in (0...num_layers) do
        layers << OGR::Tools.cast_layer(OGR_DS_GetLayer(@ptr, i))
      end

      layers
    end
    alias_method :layers, :get_layers

    def get_features
      layers.map {|l| l.features}
    end
    alias_method :features, :get_features

    def get_geometries(as_geojson=false)
      unless as_geojson
        features.map {|feature| feature.map {|f| OGR::Tools.cast_geometry(f.geometry)}}
      else
        features.map {|feature| feature.map {|f| OGR::Tools.cast_geometry(f.geometry).to_geojson}}
      end
    end
    alias_method :geometries, :get_geometries

    def get_fields
      features.map {|feature| feature.map {|f| f.fields}}
    end
    alias_method :fields, :get_fields

    def to_format(format, output_path, options={})
      raise RuntimeError.new("Output path not specified.") if output_path.nil?

      spatial_ref = options.delete :spatial_ref

      driver_options = parse_driver_options options

      unless spatial_ref
        copy format, output_path, driver_options
      else
        if spatial_ref.instance_of? OGR::SpatialReference
          copy_with_transform format, output_path, spatial_ref, driver_options
        else
          raise RuntimeError.new("Invalid spatial reference specified.")
        end
      end
    end

    def to_shp(output_path, options={})
      to_format('shapefile', output_path)
    end

    def to_csv(output_path, options={})
      to_format('csv', output_path)
    end

    def to_kml(output_path, options={})
      warn "KML output will always be in EPSG:4326" unless options[:spatial_ref].nil?

      to_format('kml', output_path, options)
    end

    def to_geojson(output_path, options={})
      to_format('geojson', output_path, options)
    end

    def parse_driver_options(options)
      tf_values = {
        true => "YES",
        false => "NO"
      }

      pointers = [].tap do |ptrs|
        options.each do |k,v|
          tf_value = tf_values[v] || v
          ptrs << FFI::MemoryPointer.from_string("#{k.to_s.upcase}=#{tf_value.upcase}")
        end
      end

      pointers << nil

      driver_options = FFI::MemoryPointer.new :pointer, pointers.size

      pointers.each_with_index do |ptr, i|
        driver_options[i].put_pointer 0, ptr
      end

      driver_options
    end
{'fraction_non_alphanumeric': 0.08858413639733136, 'fraction_numerical': 0.0016679021497405485, 'mean_word_length': 2.6266801075268815, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5293988', 'n_tokens_mistral': 3625, 'n_tokens_neox': 3465, 'n_words': 693}