class GeoRuby::Shp4r::ShpTransaction

An object returned from GeoRuby::Shp4r::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 343
def initialize(shp, dbf)
  @deleted = Hash.new
  @added = Array.new
  @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 363
def add(record)
  record_type = to_shp_type(record.geometry)
  raise 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 370
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 = Hash.new
  @added = Array.new
  
  @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 351
def delete(i)
  raise 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 396
def rollback
  @deleted = Hash.new
  @added = Array.new
  @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 357
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 672
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 680
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 627
def build_polygon(geometry,str)
  if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
    str << [geometry.length,geometry.inject(0) {|l, lr| l + lr.length}].pack("V2")
    str << geometry.inject([0]) {|a,lr| a << (a.last + lr.length)}.pack("V#{geometry.length}") #last element of the previous array is dropped

    geometry.each do |lr|
      lr.each do |point|
        str << [point.x,point.y].pack("E2")
      end
    end
  else
    #multipolygon

    num_rings = geometry.inject(0) {|l, poly| l + poly.length}
    str << [num_rings, geometry.inject(0) {|l, poly| l + poly.inject(0) {|l2,lr| l2 + lr.length} }].pack("V2")
    str << geometry.inject([0]) {|a,poly| poly.inject(a) {|a2, lr| a2 << (a2.last + lr.length)}}.pack("V#{num_rings}") #last element of the previous array is dropped

    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 652
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 591
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.inject(0) {|l, ls| l + ls.length}].pack("V2")
    str << geometry.inject([0]) {|a,ls| a << (a.last + ls.length)}.pack("V#{geometry.length}") #last element of the previous array is dropped

    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 610
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 526
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 430
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'
              if data
                "T" 
              else
                "F"
              end
            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 471
def commit_delete
  @shp_r.rewind
  header = @shp_r.read(100)
  @shp_io << header
  @shx_io << header
  index = 1
  while(!@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
  while(!@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 506
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 586
def create_bbox(geometry)
  bbox = geometry.bounding_box
  [[bbox[0].x,bbox[0].y,bbox[1].x,bbox[1].y].pack("E4"),bbox]
end
to_shp_type(geom) click to toggle source
# File lib/geo_ruby/shp4r/shp.rb, line 404
def to_shp_type(geom)
  root = if geom.is_a? GeoRuby::SimpleFeatures::Point
           "POINT"
         elsif geom.is_a? GeoRuby::SimpleFeatures::LineString
           "POLYLINE"
         elsif geom.is_a?  GeoRuby::SimpleFeatures::Polygon
           "POLYGON"
         elsif geom.is_a?  GeoRuby::SimpleFeatures::MultiPoint
           "MULTIPOINT"
         elsif geom.is_a?  GeoRuby::SimpleFeatures::MultiLineString
           "POLYLINE"
         elsif geom.is_a?  GeoRuby::SimpleFeatures::MultiPolygon
           "POLYGON"
         else
           false
         end
  return false if !root

  if geom.with_z
    root = root + "Z"
  elsif geom.with_m
    root = root + "M"
  end
  eval "ShpType::" + root
end