class GeoRuby::Shp4r::ShpTransaction

An object returned from ShpFile#transaction. Buffers updates to a Shapefile

Attributes

rollbacked[R]

Public Class Methods

new(shp, dbf) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 369
def initialize(shp, dbf)
  @deleted = {}
  @added = []
  @shp = shp
  @dbf = dbf
end

Public Instance Methods

add(record) click to toggle source

add a ShpRecord at the end

# File lib/geo_ruby/shp4r/shp.rb, line 389
def add(record)
  record_type = to_shp_type(record.geometry)
  fail IncompatibleGeometryException.new('Incompatible type') unless record_type == @shp.shp_type
  @added << record
end
commit() click to toggle source

updates the physical files

# File lib/geo_ruby/shp4r/shp.rb, line 396
def commit
  @shp.close
  @shp_r = open(@shp.file_root + '.shp', 'rb')
  @dbf_r = open(@shp.file_root + '.dbf', 'rb')
  @shp_io = open(@shp.file_root + '.shp.tmp.shp', 'wb')
  @shx_io = open(@shp.file_root + '.shx.tmp.shx', 'wb')
  @dbf_io = open(@shp.file_root + '.dbf.tmp.dbf', 'wb')
  index = commit_delete
  min_x, max_x, min_y, max_y, min_z, max_z, min_m, max_m = commit_add(index)
  commit_finalize(min_x, max_x, min_y, max_y, min_z, max_z, min_m, max_m)
  @shp_r.close
  @dbf_r.close
  @dbf_io.close
  @shp_io.close
  @shx_io.close
  FileUtils.move(@shp.file_root + '.shp.tmp.shp', @shp.file_root + '.shp')
  FileUtils.move(@shp.file_root + '.shx.tmp.shx', @shp.file_root + '.shx')
  FileUtils.move(@shp.file_root + '.dbf.tmp.dbf', @shp.file_root + '.dbf')

  @deleted = {}
  @added = []

  @shp.reload!
 end
delete(i) click to toggle source

delete a record. Does not take into account the records added in the current transaction

# File lib/geo_ruby/shp4r/shp.rb, line 377
def delete(i)
  fail UnexistantRecordException.new("Invalid index : #{i}") if @shp.record_count <= i
  @deleted[i] = true
end
rollback() click to toggle source

prevents the udpate from taking place

# File lib/geo_ruby/shp4r/shp.rb, line 422
def rollback
  @deleted = {}
  @added = []
  @rollbacked = true
end
update(i, record) click to toggle source

Update a record. In effect just a delete followed by an add.

# File lib/geo_ruby/shp4r/shp.rb, line 383
def update(i, record)
  delete(i)
  add(record)
end

Private Instance Methods

build_multi_point(geometry, str) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 697
def build_multi_point(geometry, str)
  str << [geometry.length].pack('V')
  geometry.each do |point|
    str << [point.x, point.y].pack('E2')
  end
  str
end
build_multi_point_zm(geometry, zm, range, str) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 705
def build_multi_point_zm(geometry, zm, range, str)
  str << range.pack('E2')
  geometry.each do |point|
    str << [point.instance_variable_get(zm)].pack('E')
  end
  str
end
build_polygon(geometry, str) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 644
def build_polygon(geometry, str)
  if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
    str << [geometry.length,
      geometry.reduce(0) { |a, e| a + e.length }
    ].pack('V2')
    # last element of the previous array is dropped
    str << geometry.reduce([0]) do |a, e|
      a << (a.last + e.length)
    end.pack("V#{geometry.length}")
    geometry.each do |lr|
      lr.each do |point|
        str << [point.x, point.y].pack('E2')
      end
    end
  else
    # multipolygon
    num_rings = geometry.reduce(0) { |a, e| a + e.length }
    str << [num_rings, geometry.reduce(0) { |l, poly| l + poly.reduce(0) { |l2, lr| l2 + lr.length } }].pack('V2')
    # last element of the previous array is dropped
    str << geometry.reduce([0]) do |a, e|
      e.reduce(a) { |a2, lr| a2 << (a2.last + lr.length) }
    end.pack("V#{num_rings}")
    geometry.each do |poly|
      poly.each do |lr|
        lr.each do |point|
          str << [point.x, point.y].pack('E2')
        end
      end
    end
  end
  str
end
build_polygon_zm(geometry, zm, range, str) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 677
def build_polygon_zm(geometry, zm, range, str)
  str << range.pack('E2')
  if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
    geometry.each do |lr|
      lr.each do |point|
        str << [point.instance_variable_get(zm)].pack('E')
      end
    end
  else
    geometry.each do |poly|
      poly.each do |lr|
        lr.each do |point|
          str << [point.instance_variable_get(zm)].pack('E')
        end
      end
    end
  end
  str
end
build_polyline(geometry, str) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 606
def build_polyline(geometry, str)
  if geometry.is_a? GeoRuby::SimpleFeatures::LineString
    str << [1, geometry.length, 0].pack('V3')
    geometry.each do |point|
      str << [point.x, point.y].pack('E2')
    end
  else
    # multilinestring
    str << [geometry.length, geometry.reduce(0) { |a, e| a + e.length }].pack('V2')
    str << geometry.reduce([0]) do |a, e|
      a << (a.last + e.length) # last element of the previous array is dropped
    end.pack("V#{geometry.length}")
    geometry.each do |ls|
      ls.each do |point|
        str << [point.x, point.y].pack('E2')
      end
    end
  end
  str
end
build_polyline_zm(geometry, zm, range, str) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 627
def build_polyline_zm(geometry, zm, range, str)
  str << range.pack('E2')
  if geometry.is_a? GeoRuby::SimpleFeatures::LineString
    geometry.each do |point|
      str << [point.instance_variable_get(zm)].pack('E')
    end
  else
    # multilinestring
    geometry.each do |ls|
      ls.each do |point|
        str << [point.instance_variable_get(zm)].pack('E')
      end
    end
  end
  str
end
build_shp_geometry(geometry) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 541
def build_shp_geometry(geometry)
  m_range = nil
  answer =
  case @shp.shp_type
  when ShpType::POINT
    bbox = geometry.bounding_box
    [geometry.x, geometry.y].pack('E2')
  when ShpType::POLYLINE
    str, bbox = create_bbox(geometry)
    build_polyline(geometry, str)
  when ShpType::POLYGON
    str, bbox = create_bbox(geometry)
    build_polygon(geometry, str)
  when ShpType::MULTIPOINT
    str, bbox = create_bbox(geometry)
    build_multi_point(geometry, str)
  when ShpType::POINTZ
    bbox = geometry.bounding_box
    [geometry.x, geometry.y, geometry.z, geometry.m].pack('E4')
  when ShpType::POLYLINEZ
    str, bbox = create_bbox(geometry)
    m_range = geometry.m_range
    build_polyline(geometry, str)
    build_polyline_zm(geometry, :@z, [bbox[0].z, bbox[1].z], str)
    build_polyline_zm(geometry, :@m, m_range, str)
  when ShpType::POLYGONZ
    str, bbox = create_bbox(geometry)
    m_range = geometry.m_range
    build_polygon(geometry, str)
    build_polygon_zm(geometry, :@z, [bbox[0].z, bbox[1].z], str)
    build_polygon_zm(geometry, :@m, m_range, str)
  when ShpType::MULTIPOINTZ
    str, bbox = create_bbox(geometry)
    m_range = geometry.m_range
    build_multi_point(geometry, str)
    build_multi_point_zm(geometry, :@z, [bbox[0].z, bbox[1].z], str)
    build_multi_point_zm(geometry, :@m, m_range, str)
  when ShpType::POINTM
    bbox = geometry.bounding_box
    [geometry.x, geometry.y, geometry.m].pack('E3')
  when ShpType::POLYLINEM
    str, bbox = create_bbox(geometry)
    m_range = geometry.m_range
    build_polyline(geometry, str)
    build_polyline_zm(geometry, :@m, m_range, str)
  when ShpType::POLYGONM
    str, bbox = create_bbox(geometry)
    m_range = geometry.m_range
    build_polygon(geometry, str)
    build_polygon_zm(geometry, :@m, m_range, str)
  when ShpType::MULTIPOINTM
    str, bbox = create_bbox(geometry)
    m_range = geometry.m_range
    build_multi_point(geometry, str)
    build_multi_point_zm(geometry, :@m, m_range, str)
  end
  m_range ||= [0, 0]
  [answer, bbox[0].x, bbox[1].x, bbox[0].y, bbox[1].y, bbox[0].z || 0, bbox[1].z || 0, m_range[0], m_range[1]]
end
commit_add(index) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 449
def commit_add(index)
  max_x, min_x, max_y, min_y, max_z, min_z, max_m, min_m = @shp.xmax, @shp.xmin, @shp.ymax, @shp.ymin, @shp.zmax, @shp.zmin, @shp.mmax, @shp.mmin
  @added.each do |record|
    @dbf_io << ['20'].pack('H2')
    @dbf.fields.each do |field|
      data = record.data[field.name]
      str = if field.type == 'D'
              sprintf('%04i%02i%02i', data.year, data.month, data.mday)
            elsif field.type == 'L'
              data ? 'T' :  'F'
            else
              data.to_s
            end
      @dbf_io << [str].pack("A#{field.length}")
    end

    shp_str, min_xp, max_xp, min_yp, max_yp, min_zp, max_zp, min_mp, max_mp = build_shp_geometry(record.geometry)
    max_x = max_xp if max_xp > max_x
    min_x = min_xp if min_xp < min_x
    max_y = max_yp if max_yp > max_y
    min_y = min_yp if min_yp < min_y
    max_z = max_zp if max_zp > max_z
    min_z = min_zp if min_zp < min_z
    max_m = max_mp if max_mp > max_m
    min_m = min_mp if min_mp < min_m
    length = (shp_str.length / 2 + 2).to_i # num of 16-bit words; geom type is included (+2)
    @shx_io << [(@shp_io.pos / 2).to_i, length].pack('N2')
    @shp_io << [index, length, @shp.shp_type].pack('N2V')
    @shp_io << shp_str
    index += 1
  end
  @shp_io.flush
  @shx_io.flush
  @dbf_io.flush
  [min_x, max_x, min_y, max_y, min_z, max_z, min_m, max_m]
end
commit_delete() click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 486
def commit_delete
  @shp_r.rewind
  header = @shp_r.read(100)
  @shp_io << header
  @shx_io << header
  index = 1
  until @shp_r.eof?
    icur, length = @shp_r.read(8).unpack('N2')
    unless @deleted[icur - 1]
      @shx_io << [(@shp_io.pos / 2).to_i, length].pack('N2')
      @shp_io << [index, length].pack('N2')
      @shp_io << @shp_r.read(length * 2)
      index += 1
    else
      @shp_r.seek(length * 2, IO::SEEK_CUR)
    end
  end
  @shp_io.flush
  @shx_io.flush

  @dbf_r.rewind
  @dbf_io << @dbf_r.read(@dbf.header_length)
  icur = 0
  until @dbf_r.eof?
    unless @deleted[icur]
      @dbf_io << @dbf_r.read(@dbf.record_length)
    else
      @dbf_r.seek(@dbf.record_length, IO::SEEK_CUR)
    end
    icur += 1
  end
  @dbf_io.flush
  index
end
commit_finalize(min_x, max_x, min_y, max_y, min_z, max_z, min_m, max_m) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 521
def commit_finalize(min_x, max_x, min_y, max_y, min_z, max_z, min_m, max_m)
  # update size in shp and dbf + extent and num records in dbf
  @shp_io.seek(0, IO::SEEK_END)
  shp_size = @shp_io.pos / 2
  @shx_io.seek(0, IO::SEEK_END)
  shx_size = @shx_io.pos / 2
  @shp_io.seek(24)
  @shp_io.write([shp_size].pack('N'))
  @shx_io.seek(24)
  @shx_io.write([shx_size].pack('N'))
  @shp_io.seek(36)
  @shx_io.seek(36)
  str = [min_x, min_y, max_x, max_y, min_z, max_z, min_m, max_m].pack('E8')
  @shp_io.write(str)
  @shx_io.write(str)

  @dbf_io.seek(4)
  @dbf_io.write([@dbf.record_count + @added.length - @deleted.length].pack('V'))
end
create_bbox(geometry) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 601
def create_bbox(geometry)
  bbox = geometry.bounding_box
  [[bbox[0].x, bbox[0].y, bbox[1].x, bbox[1].y].pack('E4'), bbox]
end
geom_type(geom) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 430
def geom_type(geom)
  case geom
  when GeoRuby::SimpleFeatures::Point then 'POINT'
  when GeoRuby::SimpleFeatures::LineString then 'POLYLINE'
  when GeoRuby::SimpleFeatures::Polygon then 'POLYGON'
  when GeoRuby::SimpleFeatures::MultiPoint then 'MULTIPOINT'
  when GeoRuby::SimpleFeatures::MultiLineString then 'POLYLINE'
  when GeoRuby::SimpleFeatures::MultiPolygon then 'POLYGON'
  else false
  end
end
to_shp_type(geom) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 442
def to_shp_type(geom)
  return false unless klass = geom_type(geom)
  klass += 'Z' if geom.with_z
  klass += 'M' if geom.with_m
  GeoRuby::Shp4r.const_get('ShpType::' + klass)
end