Shapefile main file: Ruby parsing library

File extension

shp

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Shapefile main file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for Ruby generated by Kaitai Struct depends on the Ruby runtime library. You have to install it before you can parse data.

The Ruby runtime library can be installed from RubyGems:

gem install kaitai-struct

Code

Parse a local file and get structure in memory:

data = ShapefileMain.from_file("path/to/local/file.shp")

Or parse structure from a string of bytes:

bytes = "\x00\x01\x02..."
data = ShapefileMain.new(Kaitai::Struct::Stream.new(bytes))

After that, one can get various attributes from the structure by invoking getter methods like:

data.records # => the size of this section of the file in bytes must equal (header.file_length * 2) - 100

Ruby source code to parse Shapefile main file

shapefile_main.rb

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

require 'kaitai/struct/struct'

unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.9')
  raise "Incompatible Kaitai Struct Ruby API: 0.9 or later is required, but you have #{Kaitai::Struct::VERSION}"
end

class ShapefileMain < Kaitai::Struct::Struct

  SHAPE_TYPE = {
    0 => :shape_type_null_shape,
    1 => :shape_type_point,
    3 => :shape_type_poly_line,
    5 => :shape_type_polygon,
    8 => :shape_type_multi_point,
    11 => :shape_type_point_z,
    13 => :shape_type_poly_line_z,
    15 => :shape_type_polygon_z,
    18 => :shape_type_multi_point_z,
    21 => :shape_type_point_m,
    23 => :shape_type_poly_line_m,
    25 => :shape_type_polygon_m,
    28 => :shape_type_multi_point_m,
    31 => :shape_type_multi_patch,
  }
  I__SHAPE_TYPE = SHAPE_TYPE.invert

  PART_TYPE = {
    0 => :part_type_triangle_strip,
    1 => :part_type_triangle_fan,
    2 => :part_type_outer_ring,
    3 => :part_type_inner_ring,
    4 => :part_type_first_ring,
    5 => :part_type_ring,
  }
  I__PART_TYPE = PART_TYPE.invert
  def initialize(_io, _parent = nil, _root = self)
    super(_io, _parent, _root)
    _read
  end

  def _read
    @header = FileHeader.new(@_io, self, @_root)
    @records = []
    i = 0
    while not @_io.eof?
      @records << Record.new(@_io, self, @_root)
      i += 1
    end
    self
  end
  class MultiPointM < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_points = @_io.read_s4le
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      @m_range = BoundsMinMax.new(@_io, self, @_root)
      @m_values = []
      (number_of_points).times { |i|
        @m_values << @_io.read_f8le
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_points
    attr_reader :points
    attr_reader :m_range
    attr_reader :m_values
  end
  class BoundingBoxXYZM < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @x = BoundsMinMax.new(@_io, self, @_root)
      @y = BoundsMinMax.new(@_io, self, @_root)
      @z = BoundsMinMax.new(@_io, self, @_root)
      @m = BoundsMinMax.new(@_io, self, @_root)
      self
    end
    attr_reader :x
    attr_reader :y
    attr_reader :z
    attr_reader :m
  end
  class Point < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @x = @_io.read_f8le
      @y = @_io.read_f8le
      self
    end
    attr_reader :x
    attr_reader :y
  end
  class Polygon < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_parts = @_io.read_s4le
      @number_of_points = @_io.read_s4le
      @parts = []
      (number_of_parts).times { |i|
        @parts << @_io.read_s4le
      }
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_parts
    attr_reader :number_of_points
    attr_reader :parts
    attr_reader :points
  end
  class BoundsMinMax < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @min = @_io.read_f8le
      @max = @_io.read_f8le
      self
    end
    attr_reader :min
    attr_reader :max
  end
  class PolyLine < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_parts = @_io.read_s4le
      @number_of_points = @_io.read_s4le
      @parts = []
      (number_of_parts).times { |i|
        @parts << @_io.read_s4le
      }
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_parts
    attr_reader :number_of_points
    attr_reader :parts
    attr_reader :points
  end
  class MultiPointZ < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_points = @_io.read_s4le
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      @z_range = BoundsMinMax.new(@_io, self, @_root)
      @z_values = []
      (number_of_points).times { |i|
        @z_values << @_io.read_f8le
      }
      @m_range = BoundsMinMax.new(@_io, self, @_root)
      @m_values = []
      (number_of_points).times { |i|
        @m_values << @_io.read_f8le
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_points
    attr_reader :points
    attr_reader :z_range
    attr_reader :z_values
    attr_reader :m_range
    attr_reader :m_values
  end
  class PolyLineZ < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_parts = @_io.read_s4le
      @number_of_points = @_io.read_s4le
      @parts = []
      (number_of_parts).times { |i|
        @parts << @_io.read_s4le
      }
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      @z_range = BoundsMinMax.new(@_io, self, @_root)
      @z_values = []
      (number_of_points).times { |i|
        @z_values << @_io.read_f8le
      }
      @m_range = BoundsMinMax.new(@_io, self, @_root)
      @m_values = []
      (number_of_points).times { |i|
        @m_values << @_io.read_f8le
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_parts
    attr_reader :number_of_points
    attr_reader :parts
    attr_reader :points
    attr_reader :z_range
    attr_reader :z_values
    attr_reader :m_range
    attr_reader :m_values
  end
  class PolygonZ < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_parts = @_io.read_s4le
      @number_of_points = @_io.read_s4le
      @parts = []
      (number_of_parts).times { |i|
        @parts << @_io.read_s4le
      }
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      @z_range = BoundsMinMax.new(@_io, self, @_root)
      @z_values = []
      (number_of_points).times { |i|
        @z_values << @_io.read_f8le
      }
      @m_range = BoundsMinMax.new(@_io, self, @_root)
      @m_values = []
      (number_of_points).times { |i|
        @m_values << @_io.read_f8le
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_parts
    attr_reader :number_of_points
    attr_reader :parts
    attr_reader :points
    attr_reader :z_range
    attr_reader :z_values
    attr_reader :m_range
    attr_reader :m_values
  end
  class BoundingBoxXY < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @x = BoundsMinMax.new(@_io, self, @_root)
      @y = BoundsMinMax.new(@_io, self, @_root)
      self
    end
    attr_reader :x
    attr_reader :y
  end
  class PointM < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @x = @_io.read_f8le
      @y = @_io.read_f8le
      @m = @_io.read_f8le
      self
    end
    attr_reader :x
    attr_reader :y
    attr_reader :m
  end
  class PolygonM < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_parts = @_io.read_s4le
      @number_of_points = @_io.read_s4le
      @parts = []
      (number_of_parts).times { |i|
        @parts << @_io.read_s4le
      }
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      @m_range = BoundsMinMax.new(@_io, self, @_root)
      @m_values = []
      (number_of_points).times { |i|
        @m_values << @_io.read_f8le
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_parts
    attr_reader :number_of_points
    attr_reader :parts
    attr_reader :points
    attr_reader :m_range
    attr_reader :m_values
  end
  class RecordHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @record_number = @_io.read_s4be
      @content_length = @_io.read_s4be
      self
    end
    attr_reader :record_number
    attr_reader :content_length
  end
  class MultiPoint < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_points = @_io.read_s4le
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_points
    attr_reader :points
  end
  class FileHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @file_code = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 39, 10].pack('C*'), file_code, _io, "/types/file_header/seq/0") if not file_code == [0, 0, 39, 10].pack('C*')
      @unused_field_1 = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 0, 0].pack('C*'), unused_field_1, _io, "/types/file_header/seq/1") if not unused_field_1 == [0, 0, 0, 0].pack('C*')
      @unused_field_2 = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 0, 0].pack('C*'), unused_field_2, _io, "/types/file_header/seq/2") if not unused_field_2 == [0, 0, 0, 0].pack('C*')
      @unused_field_3 = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 0, 0].pack('C*'), unused_field_3, _io, "/types/file_header/seq/3") if not unused_field_3 == [0, 0, 0, 0].pack('C*')
      @unused_field_4 = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 0, 0].pack('C*'), unused_field_4, _io, "/types/file_header/seq/4") if not unused_field_4 == [0, 0, 0, 0].pack('C*')
      @unused_field_5 = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 0, 0].pack('C*'), unused_field_5, _io, "/types/file_header/seq/5") if not unused_field_5 == [0, 0, 0, 0].pack('C*')
      @file_length = @_io.read_s4be
      @version = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([232, 3, 0, 0].pack('C*'), version, _io, "/types/file_header/seq/7") if not version == [232, 3, 0, 0].pack('C*')
      @shape_type = Kaitai::Struct::Stream::resolve_enum(ShapefileMain::SHAPE_TYPE, @_io.read_s4le)
      @bounding_box = BoundingBoxXYZM.new(@_io, self, @_root)
      self
    end

    ##
    # corresponds to s4be value of 9994
    attr_reader :file_code
    attr_reader :unused_field_1
    attr_reader :unused_field_2
    attr_reader :unused_field_3
    attr_reader :unused_field_4
    attr_reader :unused_field_5
    attr_reader :file_length

    ##
    # corresponds to s4le value of 1000
    attr_reader :version
    attr_reader :shape_type
    attr_reader :bounding_box
  end
  class PointZ < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @x = @_io.read_f8le
      @y = @_io.read_f8le
      @z = @_io.read_f8le
      @m = @_io.read_f8le
      self
    end
    attr_reader :x
    attr_reader :y
    attr_reader :z
    attr_reader :m
  end
  class Record < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @header = RecordHeader.new(@_io, self, @_root)
      @contents = RecordContents.new(@_io, self, @_root)
      self
    end
    attr_reader :header

    ##
    # the size of this contents section in bytes must equal header.content_length * 2
    attr_reader :contents
  end
  class RecordContents < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @shape_type = Kaitai::Struct::Stream::resolve_enum(ShapefileMain::SHAPE_TYPE, @_io.read_s4le)
      if shape_type != :shape_type_null_shape
        case shape_type
        when :shape_type_poly_line_z
          @shape_parameters = PolyLineZ.new(@_io, self, @_root)
        when :shape_type_multi_patch
          @shape_parameters = MultiPatch.new(@_io, self, @_root)
        when :shape_type_poly_line_m
          @shape_parameters = PolyLineM.new(@_io, self, @_root)
        when :shape_type_polygon
          @shape_parameters = Polygon.new(@_io, self, @_root)
        when :shape_type_polygon_z
          @shape_parameters = PolygonZ.new(@_io, self, @_root)
        when :shape_type_point_z
          @shape_parameters = PointZ.new(@_io, self, @_root)
        when :shape_type_poly_line
          @shape_parameters = PolyLine.new(@_io, self, @_root)
        when :shape_type_point_m
          @shape_parameters = PointM.new(@_io, self, @_root)
        when :shape_type_polygon_m
          @shape_parameters = PolygonM.new(@_io, self, @_root)
        when :shape_type_multi_point
          @shape_parameters = MultiPoint.new(@_io, self, @_root)
        when :shape_type_point
          @shape_parameters = Point.new(@_io, self, @_root)
        when :shape_type_multi_point_m
          @shape_parameters = MultiPointM.new(@_io, self, @_root)
        when :shape_type_multi_point_z
          @shape_parameters = MultiPointZ.new(@_io, self, @_root)
        end
      end
      self
    end
    attr_reader :shape_type
    attr_reader :shape_parameters
  end
  class MultiPatch < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_parts = @_io.read_s4le
      @number_of_points = @_io.read_s4le
      @parts = []
      (number_of_parts).times { |i|
        @parts << @_io.read_s4le
      }
      @part_types = []
      (number_of_parts).times { |i|
        @part_types << Kaitai::Struct::Stream::resolve_enum(ShapefileMain::PART_TYPE, @_io.read_s4le)
      }
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      @z_range = BoundsMinMax.new(@_io, self, @_root)
      @z_values = []
      (number_of_points).times { |i|
        @z_values << @_io.read_f8le
      }
      @m_range = BoundsMinMax.new(@_io, self, @_root)
      @m_values = []
      (number_of_points).times { |i|
        @m_values << @_io.read_f8le
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_parts
    attr_reader :number_of_points
    attr_reader :parts
    attr_reader :part_types
    attr_reader :points
    attr_reader :z_range
    attr_reader :z_values
    attr_reader :m_range
    attr_reader :m_values
  end
  class PolyLineM < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @bounding_box = BoundingBoxXY.new(@_io, self, @_root)
      @number_of_parts = @_io.read_s4le
      @number_of_points = @_io.read_s4le
      @parts = []
      (number_of_parts).times { |i|
        @parts << @_io.read_s4le
      }
      @points = []
      (number_of_points).times { |i|
        @points << Point.new(@_io, self, @_root)
      }
      @m_range = BoundsMinMax.new(@_io, self, @_root)
      @m_values = []
      (number_of_points).times { |i|
        @m_values << @_io.read_f8le
      }
      self
    end
    attr_reader :bounding_box
    attr_reader :number_of_parts
    attr_reader :number_of_points
    attr_reader :parts
    attr_reader :points
    attr_reader :m_range
    attr_reader :m_values
  end
  attr_reader :header

  ##
  # the size of this section of the file in bytes must equal (header.file_length * 2) - 100
  attr_reader :records
end