# ----------------------------------------------------------------------------- # # Well-known binary generator for RGeo # # ----------------------------------------------------------------------------- # Copyright 2010 Daniel Azuma # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the copyright holder, nor the names of any other # contributors to this software, may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- ; module RGeo module WKRep # This class provides the functionality of serializing a geometry as # WKB (well-known binary) format. You may also customize the # serializer to generate PostGIS EWKB extensions to the output, or to # follow the Simple Features Specification 1.2 extensions for Z and M # coordinates. # # To use this class, create an instance with the desired settings and # customizations, and call the generate method. # # === Configuration options # # The following options are recognized. These can be passed to the # constructor, or set on the object afterwards. # # :type_format:: # The format for type codes. Possible values are :wkb11, # indicating SFS 1.1 WKB (i.e. no Z or M values); :ewkb, # indicating the PostGIS EWKB extensions (i.e. Z and M presence # flagged by the two high bits of the type code, and support for # embedded SRID); or :wkb12 (indicating SFS 1.2 WKB # (i.e. Z and M presence flagged by adding 1000 and/or 2000 to # the type code.) Default is :wkb11. # :emit_ewkb_srid:: # If true, embed the SRID in the toplevel geometry. Available only # if :type_format is :ewkb. Default is false. # :hex_format:: # If true, output a hex string instead of a byte string. # Default is false. # :little_endian:: # If true, output little endian (NDR) byte order. If false, output # big endian (XDR), or network byte order. Default is false. class WKBGenerator # :stopdoc: TYPE_CODES = { Feature::Point => 1, Feature::LineString => 2, Feature::LinearRing => 2, Feature::Line => 2, Feature::Polygon => 3, Feature::MultiPoint => 4, Feature::MultiLineString => 5, Feature::MultiPolygon => 6, Feature::GeometryCollection => 7, } # :startdoc: # Create and configure a WKB generator. See the WKBGenerator # documentation for the options that can be passed. def initialize(opts_={}) @type_format = opts_[:type_format] || :wkb11 @emit_ewkb_srid = opts_[:emit_ewkb_srid] ? true : false if @type_format == :ewkb @hex_format = opts_[:hex_format] ? true : false @little_endian = opts_[:little_endian] ? true : false end # Returns the format for type codes. See WKBGenerator for details. def type_format @type_format end # Sets the format for type codes. See WKBGenerator for details. def type_format=(value_) @type_format = value_ end # Returns whether SRID is embedded. See WKBGenerator for details. def emit_ewkb_srid? @emit_ewkb_srid end # Sets whether SRID is embedded. Available only when the type_format # is :ewkb. See WKBGenerator for details. def emit_ewkb_srid=(value_) @emit_ewkb_srid = @type_format == :ewkb && value_ end # Returns whether output is converted to hex. # See WKBGenerator for details. def hex_format? @hex_format end # Sets whether output is converted to hex. # See WKBGenerator for details. def hex_format=(value_) @hex_format = value_ ? true : false end # Returns whether output is little-endian (NDR). # See WKBGenerator for details. def little_endian? @little_endian end # Sets whether output is little-endian (NDR). # See WKBGenerator for details. def little_endian=(value_) @little_endian = value_ ? true : false end # Generate and return the WKB format for the given geometry object, # according to the current settings. def generate(obj_) factory_ = obj_.factory if @type_format == :ewkb || @type_format == :wkb12 @cur_has_z = factory_.has_capability?(:z_coordinate) @cur_has_m = factory_.has_capability?(:m_coordinate) else @cur_has_z = nil @cur_has_m = nil end @cur_dims = 2 + (@cur_has_z ? 1 : 0) + (@cur_has_m ? 1 : 0) _start_emitter _generate_feature(obj_, true) _finish_emitter end def _generate_feature(obj_, toplevel_=false) # :nodoc: _emit_byte(@little_endian ? 1 : 0) type_ = obj_.geometry_type type_code_ = TYPE_CODES[type_] unless type_code_ raise Error::ParseError, "Unrecognized Geometry Type: #{type_}" end emit_srid_ = false if @type_format == :ewkb type_code_ |= 0x80000000 if @cur_has_z type_code_ |= 0x40000000 if @cur_has_m if @emit_ewkb_srid && toplevel_ type_code_ |= 0x20000000 emit_srid_ = true end elsif @type_format == :wkb12 type_code_ += 1000 if @cur_has_z type_code_ += 2000 if @cur_has_m end _emit_integer(type_code_) _emit_integer(obj_.srid) if emit_srid_ if type_ == Feature::Point _emit_doubles(_point_coords(obj_)) elsif type_.subtype_of?(Feature::LineString) _emit_line_string_coords(obj_) elsif type_ == Feature::Polygon exterior_ring_ = obj_.exterior_ring if exterior_ring_.is_empty? _emit_integer(0) else _emit_integer(1 + obj_.num_interior_rings) _emit_line_string_coords(exterior_ring_) obj_.interior_rings.each{ |r_| _emit_line_string_coords(r_) } end elsif type_ == Feature::GeometryCollection _emit_integer(obj_.num_geometries) obj_.each{ |g_| _generate_feature(g_) } elsif type_ == Feature::MultiPoint _emit_integer(obj_.num_geometries) obj_.each{ |g_| _generate_feature(g_) } elsif type_ == Feature::MultiLineString _emit_integer(obj_.num_geometries) obj_.each{ |g_| _generate_feature(g_) } elsif type_ == Feature::MultiPolygon _emit_integer(obj_.num_geometries) obj_.each{ |g_| _generate_feature(g_) } end end def _point_coords(obj_, array_=[]) # :nodoc: array_ << obj_.x array_ << obj_.y array_ << obj_.z if @cur_has_z array_ << obj_.m if @cur_has_m array_ end def _emit_line_string_coords(obj_) # :nodoc: array_ = [] obj_.points.each{ |p_| _point_coords(p_, array_) } _emit_integer(obj_.num_points) _emit_doubles(array_) end def _start_emitter # :nodoc: @cur_array = [] end def _emit_byte(value_) # :nodoc: @cur_array << [value_].pack("C") end def _emit_integer(value_) # :nodoc: @cur_array << [value_].pack(@little_endian ? 'V' : 'N') end def _emit_doubles(array_) # :nodoc: @cur_array << array_.pack(@little_endian ? 'E*' : 'G*') end def _finish_emitter # :nodoc: str_ = @cur_array.join @cur_array = nil @hex_format ? str_.unpack("H*")[0] : str_ end end end end