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.
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
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
# 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