# ----------------------------------------------------------------------------- # # Feature type management and casting # # ----------------------------------------------------------------------------- # Copyright 2010-2012 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 Feature # All geometry implementations MUST include this submodule. # This serves as a marker that may be used to test an object for # feature-ness. module Instance end # These methods are available as module methods (not instance methods) # of the various feature types. # For example, you may determine whether a feature object is a # point by calling: # # ::RGeo::Feature::Point.check_type(object) # # A corresponding === operator is provided so you can use the type # modules in a case-when clause. # # You may also use the presence of this module to determine whether # a particular object is a feature type: # # object.kind_of?(::RGeo::Feature::Type) module Type # Deprecated alias for RGeo::Feature::Instance Instance = Feature::Instance # Returns true if the given object is this type or a subtype # thereof, or if it is a feature object whose geometry_type is # this type or a subtype thereof. # # Note that feature objects need not actually include this module. def check_type(rhs_) rhs_ = rhs_.geometry_type if rhs_.kind_of?(Feature::Instance) rhs_.kind_of?(Type) && (rhs_ == self || rhs_.include?(self)) end alias_method :===, :check_type # Returns true if this type is the same type or a subtype of the # given type. def subtype_of?(type_) self == type_ || self.include?(type_) end # Returns the supertype of this type. The supertype of Geometry # is nil. def supertype @supertype end # Iterates over the known immediate subtypes of this type. def each_immediate_subtype(&block_) @subtypes.each(&block_) if @subtypes end # Returns the OpenGIS type name of this type. def type_name self.name.sub('RGeo::Feature::', '') end def _add_subtype(type_) # :nodoc: (@subtypes ||= []) << type_ end def self.extended(type_) # :nodoc: supertype_ = type_.included_modules.find{ |m_| m_.kind_of?(self) } type_.instance_variable_set(:@supertype, supertype_) supertype_._add_subtype(type_) if supertype_ end end class << self # Cast the given object according to the given parameters, if # possible, and return the resulting object. If the requested cast # is not possible, nil is returned. # # Parameters may be provided as a hash, or as separate arguments. # Hash keys are as follows: # # [:factory] # Set the factory to the given factory. If this argument is not # given, the original object's factory is kept. # [:type] # Cast to the given type, which must be a module in the # RGeo::Feature namespace. If this argument is not given, the # result keeps the same type as the original. # [:project] # If this is set to true, and both the original and new factories # support proj4 projections, then the cast will also cause the # coordinates to be transformed between those two projections. # If set to false, the coordinates are not modified. Default is # false. # [:keep_subtype] # Value must be a boolean indicating whether to keep the subtype # of the original. If set to false, casting to a particular type # always casts strictly to that type, even if the old type is a # subtype of the new type. If set to true, the cast retains the # subtype in that case. For example, casting a LinearRing to a # LineString will normally yield a LineString, even though # LinearRing is already a more specific subtype. If you set this # value to true, the casted object will remain a LinearRing. # Default is false. # [:force_new] # Always return a newly-created object, even if neither the type # nor factory is modified. Normally, if this is set to false, and # a cast is not set to modify either the factory or type, the # original object itself is returned. Setting this flag to true # causes cast to return a clone in that case. Default is false. # # You may also pass the new factory, the new type, and the flags # as separate arguments. In this case, the flag names must be # passed as symbols, and their effect is the same as setting their # values to true. You can even combine separate arguments and hash # arguments. For example, the following three calls are equivalent: # # RGeo::Feature.cast(geom, :type => RGeo::Feature::Point, :project => true) # RGeo::Feature.cast(geom, RGeo::Feature::Point, :project => true) # RGeo::Feature.cast(geom, RGeo::Feature::Point, :project) # # RGeo provides a default casting algorithm. Individual feature # implementation factories may override this and customize the # casting behavior by defining the override_cast method. See # ::RGeo::Feature::Factory#override_cast for more details. def cast(obj_, *params_) # Interpret params factory_ = obj_.factory type_ = obj_.geometry_type opts_ = {} params_.each do |param_| case param_ when Factory::Instance opts_[:factory] = param_ when Type opts_[:type] = param_ when ::Symbol opts_[param_] = true when ::Hash opts_.merge!(param_) end end force_new_ = opts_[:force_new] keep_subtype_ = opts_[:keep_subtype] project_ = opts_[:project] nfactory_ = opts_.delete(:factory) || factory_ ntype_ = opts_.delete(:type) || type_ # Let the factory override if nfactory_.respond_to?(:override_cast) override_ = nfactory_.override_cast(obj_, ntype_, opts_) return override_ unless override_ == false end # Default algorithm ntype_ = type_ if keep_subtype_ && type_.include?(ntype_) if ntype_ == type_ # Types are the same if nfactory_ == factory_ force_new_ ? obj_.dup : obj_ else if type_ == Point proj_ = nproj_ = nil if project_ proj_ = factory_.proj4 nproj_ = nfactory_.proj4 end hasz_ = factory_.property(:has_z_coordinate) nhasz_ = nfactory_.property(:has_z_coordinate) if proj_ && nproj_ coords_ = CoordSys::Proj4.transform_coords(proj_, nproj_, obj_.x, obj_.y, hasz_ ? obj_.z : nil) coords_ << (hasz_ ? obj_.z : 0.0) if nhasz_ && coords_.size < 3 else coords_ = [obj_.x, obj_.y] coords_ << (hasz_ ? obj_.z : 0.0) if nhasz_ end coords_ << (factory_.property(:has_m_coordinate) ? obj_.m : 0.0) if nfactory_.property(:has_m_coordinate) nfactory_.point(*coords_) elsif type_ == Line nfactory_.line(cast(obj_.start_point, nfactory_, opts_), cast(obj_.end_point, nfactory_, opts_)) elsif type_ == LinearRing nfactory_.linear_ring(obj_.points.map{ |p_| cast(p_, nfactory_, opts_) }) elsif type_ == LineString nfactory_.line_string(obj_.points.map{ |p_| cast(p_, nfactory_, opts_) }) elsif type_ == Polygon nfactory_.polygon(cast(obj_.exterior_ring, nfactory_, opts_), obj_.interior_rings.map{ |r_| cast(r_, nfactory_, opts_) }) elsif type_ == MultiPoint nfactory_.multi_point(obj_.map{ |g_| cast(g_, nfactory_, opts_) }) elsif type_ == MultiLineString nfactory_.multi_line_string(obj_.map{ |g_| cast(g_, nfactory_, opts_) }) elsif type_ == MultiPolygon nfactory_.multi_polygon(obj_.map{ |g_| cast(g_, nfactory_, opts_) }) elsif type_ == GeometryCollection nfactory_.collection(obj_.map{ |g_| cast(g_, nfactory_, opts_) }) else nil end end else # Types are different if ntype_ == Point && (type_ == MultiPoint || type_ == GeometryCollection) || (ntype_ == Line || ntype_ == LineString || ntype_ == LinearRing) && (type_ == MultiLineString || type_ == GeometryCollection) || ntype_ == Polygon && (type_ == MultiPolygon || type_ == GeometryCollection) then if obj_.num_geometries == 1 cast(obj_.geometry_n(0), nfactory_, ntype_, opts_) else nil end elsif ntype_ == Point nil elsif ntype_ == Line if type_ == LineString && obj_.num_points == 2 nfactory_.line(cast(obj_.point_n(0), nfactory_, opts_), cast(obj_.point_n(1), nfactory_, opts_)) else nil end elsif ntype_ == LinearRing if type_ == LineString nfactory_.linear_ring(obj_.points.map{ |p_| cast(p_, nfactory_, opts_) }) else nil end elsif ntype_ == LineString if type_ == Line || type_ == LinearRing nfactory_.line_string(obj_.points.map{ |p_| cast(p_, nfactory_, opts_) }) else nil end elsif ntype_ == MultiPoint if type_ == Point nfactory_.multi_point([cast(obj_, nfactory_, opts_)]) elsif type_ == GeometryCollection nfactory_.multi_point(obj_.map{ |g_| cast(p_, nfactory_, opts_) }) else nil end elsif ntype_ == MultiLineString if type_ == Line || type_ == LinearRing || type_ == LineString nfactory_.multi_line_string([cast(obj_, nfactory_, opts_)]) elsif type_ == GeometryCollection nfactory_.multi_line_string(obj_.map{ |g_| cast(p_, nfactory_, opts_) }) else nil end elsif ntype_ == MultiPolygon if type_ == Polygon nfactory_.multi_polygon([cast(obj_, nfactory_, opts_)]) elsif type_ == GeometryCollection nfactory_.multi_polygon(obj_.map{ |g_| cast(p_, nfactory_, opts_) }) else nil end elsif ntype_ == GeometryCollection if type_ == MultiPoint || type_ == MultiLineString || type_ == MultiPolygon nfactory_.collection(obj_.map{ |g_| cast(p_, nfactory_, opts_) }) else nfactory_.collection([cast(obj_, nfactory_, opts_)]) end else nil end end end end end end