module GeoRuby module SimpleFeatures # Raised when an error in the EWKB string is detected class EWKBFormatError < StandardError end # Parses EWKB strings and notifies of events (such as the beginning of the definition of geometry, the value of the SRID...) the factory passed as argument to the constructor. # # =Example # factory = GeometryFactory::new # ewkb_parser = EWKBParser::new(factory) # ewkb_parser.parse() # geometry = @factory.geometry # # You can also use directly the static method Geometry.from_ewkb class EWKBParser def initialize(factory) @factory = factory end # Parses the ewkb string passed as argument and notifies the factory of events def parse(ewkb) @factory.reset @with_z = false @with_m = false @unpack_structure = UnpackStructure.new(ewkb) parse_geometry @unpack_structure.done @srid = nil end private def parse_geometry @unpack_structure.endianness = @unpack_structure.read_byte geometry_type = @unpack_structure.read_uint if (geometry_type & Z_MASK) != 0 @with_z = true geometry_type = geometry_type & ~Z_MASK end if (geometry_type & M_MASK) != 0 @with_m = true geometry_type = geometry_type & ~M_MASK end if (geometry_type & SRID_MASK) != 0 @srid = @unpack_structure.read_uint geometry_type = geometry_type & ~SRID_MASK else # to manage multi geometries : the srid is not present in sub_geometries, therefore we take the srid of the parent ; if it is the root, we take the default srid @srid ||= DEFAULT_SRID end case geometry_type when 1 parse_point when 2 parse_line_string when 3 parse_polygon when 4 parse_multi_point when 5 parse_multi_line_string when 6 parse_multi_polygon when 7 parse_geometry_collection else fail EWKBFormatError.new('Unknown geometry type') end end def parse_geometry_collection parse_multi_geometries(GeometryCollection) end def parse_multi_polygon parse_multi_geometries(MultiPolygon) end def parse_multi_line_string parse_multi_geometries(MultiLineString) end def parse_multi_point parse_multi_geometries(MultiPoint) end def parse_multi_geometries(geometry_type) @factory.begin_geometry(geometry_type, @srid) num_geometries = @unpack_structure.read_uint 1.upto(num_geometries) { parse_geometry } @factory.end_geometry(@with_z, @with_m) end def parse_polygon @factory.begin_geometry(Polygon, @srid) num_linear_rings = @unpack_structure.read_uint 1.upto(num_linear_rings) { parse_linear_ring } @factory.end_geometry(@with_z, @with_m) end def parse_linear_ring parse_point_list(LinearRing) end def parse_line_string parse_point_list(LineString) end # used to parse line_strings and linear_rings def parse_point_list(geometry_type) @factory.begin_geometry(geometry_type, @srid) num_points = @unpack_structure.read_uint 1.upto(num_points) { parse_point } @factory.end_geometry(@with_z, @with_m) end def parse_point @factory.begin_geometry(Point, @srid) x, y = *@unpack_structure.read_point if ! (@with_z || @with_m) # most common case probably @factory.add_point_x_y(x, y) elsif @with_m && @with_z z = @unpack_structure.read_double m = @unpack_structure.read_double @factory.add_point_x_y_z_m(x, y, z, m) elsif @with_z z = @unpack_structure.read_double @factory.add_point_x_y_z(x, y, z) else m = @unpack_structure.read_double @factory.add_point_x_y_m(x, y, m) end @factory.end_geometry(@with_z, @with_m) end end # Parses HexEWKB strings. In reality, it just transforms the HexEWKB string into the equivalent EWKB string and lets the EWKBParser do the actual parsing. class HexEWKBParser < EWKBParser # parses an HexEWKB string def parse(hexewkb) super(decode_hex(hexewkb)) end # transforms a HexEWKB string into an EWKB string def decode_hex(hexewkb) [hexewkb].pack('H*') end end class UnpackStructure #:nodoc: NDR = 1 XDR = 0 def initialize(ewkb) @position = 0 @ewkb = ewkb end def done fail EWKBFormatError.new('Trailing data') if @position != @ewkb.length end def read_point i = @position @position += 16 fail EWKBFormatError.new('Truncated data') if @ewkb.length < @position @ewkb.unpack("@#{i}#{@double_mark}#{@double_mark}@*") end def read_double i = @position @position += 8 fail EWKBFormatError.new('Truncated data') if @ewkb.length < @position @ewkb.unpack("@#{i}#{@double_mark}@*").first end def read_uint i = @position @position += 4 fail EWKBFormatError.new('Truncated data') if @ewkb.length < @position @ewkb.unpack("@#{i}#{@uint_mark}@*").first end def read_byte i = @position @position += 1 fail EWKBFormatError.new('Truncated data') if @ewkb.length < @position @ewkb.unpack("@#{i}C@*").first end def endianness=(byte_order) if (byte_order == NDR) @uint_mark = 'V' @double_mark = 'E' elsif (byte_order == XDR) @uint_mark = 'N' @double_mark = 'G' end end end end end