# frozen_string_literal: true # ----------------------------------------------------------------------------- # # Access to geographic data factories # # ----------------------------------------------------------------------------- module RGeo module Geographic class << self # Creates and returns a geographic factory that does not include a # a projection, and which performs calculations assuming a # spherical earth. In other words, geodesics are treated as great # circle arcs, and geometric calculations are handled accordingly. # Size and distance calculations report results in meters. # This implementation is thus ideal for everyday calculations on # the globe in which good accuracy is desired, but in which it is # not deemed necessary to perform the complex ellipsoidal # calculations needed for greater precision. # # The maximum error is about 0.5 percent, for objects and # calculations that span a significant percentage of the globe, due # to distortion caused by rotational flattening of the earth. For # calculations that span a much smaller area, the error can drop to # a few meters or less. # # === Limitations # # This implementation does not implement some of the more advanced # geometric operations. In particular: # # * Relational operators such as Feature::Geometry#intersects? are # not implemented for most types. # * Relational constructors such as Feature::Geometry#union are # not implemented for most types. # * Buffer, convex hull, and envelope calculations are not # implemented for most types. Boundaries are available except for # GeometryCollection. # * Length calculations are available, but areas are not. Distances # are available only between points. # * Equality and simplicity evaluation are implemented for some but # not all types. # * Assertions for polygons and multipolygons are not implemented. # # Unimplemented operations will return nil if invoked. # # === Options # # You may use the following options when creating a spherical # factory: # # [:has_z_coordinate] # Support a Z coordinate. Default is false. # [:has_m_coordinate] # Support an M coordinate. Default is false. # [:uses_lenient_assertions] # If set to true, assertion checking is disabled. This includes # simplicity checking on LinearRing, and validity checks on # Polygon and MultiPolygon. This may speed up creation of certain # objects, at the expense of not doing the proper checking for # OGC compliance. Default is false. # [:buffer_resolution] # The resolution of buffers around geometries created by this # factory. This controls the number of line segments used to # approximate curves. The default is 1, which causes, for # example, the buffer around a point to be approximated by a # 4-sided polygon. A resolution of 2 would cause that buffer # to be approximated by an 8-sided polygon. The exact behavior # for different kinds of buffers is not specified precisely, # but in general the value is taken as the number of segments # per 90-degree curve. # [:proj4] # Provide the coordinate system in Proj4 format. You may pass # either an RGeo::CoordSys::Proj4 object, or a string or hash # containing the Proj4 parameters. This coordinate system must be # a geographic (lat/long) coordinate system. The default is the # "popular visualization CRS" (EPSG 4055), represented by # "+proj=longlat +a=6378137 +b=6378137 +towgs84=0,0,0,0,0,0,0 +no_defs". # Has no effect if Proj4 is not available. # [:coord_sys] # Provide a coordinate system in OGC format, either as an object # (one of the CoordSys::CS classes) or as a string in WKT format. # This coordinate system must be a GeographicCoordinateSystem. # The default is the "popular visualization CRS" (EPSG 4055). # [:srid] # The SRID that should be returned by features from this factory. # Default is 4055, indicating EPSG 4055, the "popular # visualization crs". You may alternatively wish to set the srid # to 4326, indicating the WGS84 crs, but note that that value # implies an ellipsoidal datum, not a spherical datum. # [:srs_database] # Optional. If provided, the object should respond to #get and # #clear_cache. If both this and an SRID are # provided, they are used to look up the proj4 and coord_sys # objects from a spatial reference system database. # [:wkt_parser] # Configure the parser for WKT. The value is a hash of # configuration parameters for WKRep::WKTParser.new. Default is # the empty hash, indicating the default configuration for # WKRep::WKTParser. # [:wkb_parser] # Configure the parser for WKB. The value is a hash of # configuration parameters for WKRep::WKBParser.new. Default is # the empty hash, indicating the default configuration for # WKRep::WKBParser. # [:wkt_generator] # Configure the generator for WKT. The value is a hash of # configuration parameters for WKRep::WKTGenerator.new. # Default is {:convert_case => :upper}. # [:wkb_generator] # Configure the generator for WKT. The value is a hash of # configuration parameters for WKRep::WKTGenerator.new. # Default is the empty hash, indicating the default configuration # for WKRep::WKBGenerator. def spherical_factory(opts = {}) proj4 = opts[:proj4] coord_sys = opts[:coord_sys] srid = opts[:srid] if (!proj4 || !coord_sys) && srid && (db_ = opts[:srs_database]) entry_ = db_.get(srid.to_i) if entry_ proj4 ||= entry_.proj4 coord_sys ||= entry_.coord_sys end end srid ||= coord_sys.authority_code if coord_sys Geographic::Factory.new("Spherical", has_z_coordinate: opts[:has_z_coordinate], has_m_coordinate: opts[:has_m_coordinate], proj4: proj4 || proj_4055, coord_sys: coord_sys || coord_sys_4055, uses_lenient_assertions: opts[:uses_lenient_assertions], buffer_resolution: opts[:buffer_resolution], wkt_parser: opts[:wkt_parser], wkb_parser: opts[:wkb_parser], wkt_generator: opts[:wkt_generator], wkb_generator: opts[:wkb_generator], srid: (srid || 4055).to_i) end # Creates and returns a geographic factory that is designed for # visualization applications that use Google or Bing maps, or any # other visualization systems that use the same projection. It # includes a projection factory that matches the projection used # by those mapping systems. # # Like all geographic factories, this one creates features using # latitude-longitude values. However, calculations such as # intersections are done in the projected coordinate system, and # size and distance calculations report results in the projected # units. # # The behavior of the simple_mercator factory could also be obtained # using a projected_factory with appropriate Proj4 specifications. # However, the simple_mercator implementation is done without # actually requiring the Proj4 library. The projections are simple # enough to be implemented in pure ruby. # # === About the coordinate system # # Many popular visualization technologies, such as Google and Bing # maps, actually use two coordinate systems. The first is the # standard WSG84 lat-long system used by the GPS and represented # by EPSG 4326. Most API calls and input-output in these mapping # technologies utilize this coordinate system. The second is a # Mercator projection based on a "sphericalization" of the WGS84 # lat-long system. This projection is the basis of the map's screen # and tiling coordinates, and has been assigned EPSG 3857. # # This factory represents both coordinate systems. The main factory # produces data in the lat-long system and reports SRID 4326, and # the projected factory produces data in the projection and reports # SRID 3857. Latitudes are restricted to the range # (-85.05112877980659, 85.05112877980659), which conveniently # results in a square projected domain. # # === Options # # You may use the following options when creating a simple_mercator # factory: # # [:has_z_coordinate] # Support a Z coordinate. Default is false. # [:has_m_coordinate] # Support an M coordinate. Default is false. # [:wkt_parser] # Configure the parser for WKT. The value is a hash of # configuration parameters for WKRep::WKTParser.new. Default is # the empty hash, indicating the default configuration for # WKRep::WKTParser. # [:wkb_parser] # Configure the parser for WKB. The value is a hash of # configuration parameters for WKRep::WKBParser.new. Default is # the empty hash, indicating the default configuration for # WKRep::WKBParser. # [:wkt_generator] # Configure the generator for WKT. The value is a hash of # configuration parameters for WKRep::WKTGenerator.new. # Default is {:convert_case => :upper}. # [:wkb_generator] # Configure the generator for WKT. The value is a hash of # configuration parameters for WKRep::WKTGenerator.new. # Default is the empty hash, indicating the default configuration # for WKRep::WKBGenerator. # # You may also provide options understood by the underlying # projected Cartesian factory. For example, if GEOS is used for the # projected factory, you may also set the # :lenient_multi_polygon_assertions and # :buffer_resolution options. See RGeo::Geos.factory for # more details. def simple_mercator_factory(opts = {}) factory = Geographic::Factory.new("Projected", proj4: proj_4326, coord_sys: coord_sys_4326, srid: 4326, wkt_parser: opts[:wkt_parser], wkb_parser: opts[:wkb_parser], wkt_generator: opts[:wkt_generator], wkb_generator: opts[:wkb_generator], has_z_coordinate: opts[:has_z_coordinate], has_m_coordinate: opts[:has_m_coordinate]) projector = Geographic::SimpleMercatorProjector.new(factory, buffer_resolution: opts[:buffer_resolution], lenient_multi_polygon_assertions: opts[:lenient_multi_polygon_assertions], uses_lenient_assertions: opts[:uses_lenient_assertions], has_z_coordinate: opts[:has_z_coordinate], has_m_coordinate: opts[:has_m_coordinate]) factory.projector = projector factory end # Creates and returns a geographic factory that includes a # projection specified by a Proj4 coordinate system. Like all # geographic factories, this one creates features using latitude- # longitude values. However, calculations such as intersections are # done in the projected coordinate system, and size and distance # calculations report results in the projected units. Thus, this # factory actually includes two factories representing different # coordinate systems: the main factory representing the geographic # lat-long coordinate system, and an auxiliary "projection factory" # representing the projected coordinate system. # # This implementation is intended for advanced GIS applications # requiring greater control over the projection being used. # # === Options # # When creating a projected implementation, you must provide enough # information to construct a Proj4 specification for the projection. # Generally, this means you will provide either the projection's # factory itself (via the :projection_factory option), in # which case the factory must include a Proj4 coordinate system; # or, alternatively, you should provide the Proj4 coordinate system # and let this method construct a projection factory for you (which # it will do using the preferred Cartesian factory generator). # If you choose this second method, you may provide the proj4 # directly via the :projection_proj4 option, or indirectly # by providing both an :srid and a :srs_database # to use to look up the coordinate system. # # Following are detailed descriptions of the various options you can # pass to this method. # # [:projection_factory] # Specify an existing Cartesian factory to use for the projection. # This factory must have a non-nil Proj4. If this is provided, any # :projection_proj4, :projection_coord_sys, and # :projection_srid are ignored. # [:projection_proj4] # Specify a Proj4 projection to use to construct the projection # factory. This may be specified as a CoordSys::Proj4 object, or # as a Proj4 string or hash representation. # [:projection_coord_sys] # Specify a OGC coordinate system for the projection. This may be # specified as an RGeo::CoordSys::CS::GeographicCoordinateSystem # object, or as a String in OGC WKT format. Optional. # [:projection_srid] # The SRID value to use for the projection factory. Defaults to # the given projection coordinate system's authority code, or to # 0 if no projection coordinate system is known. # [:proj4] # A proj4 projection for the geographic (lat-lon) factory. You may # pass either an RGeo::CoordSys::Proj4 object, or a string or hash # containing the Proj4 parameters. This coordinate system must be # a geographic (lat/long) coordinate system. It defaults to the # geographic part of the projection factory's coordinate system. # Generally, you should leave it at the default unless you want # the geographic coordinate system to be based on a different # horizontal datum than the projection. # [:coord_sys] # An OGC coordinate system for the geographic (lat-lon) factory, # which may be an RGeo::CoordSys::CS::GeographicCoordinateSystem # object or a string in OGC WKT format. It defaults to the # geographic system embedded in the projection coordinate system. # Generally, you should leave it at the default unless you want # the geographic coordinate system to be based on a different # horizontal datum than the projection. # [:srid] # The SRID value to use for the main geographic factory. Defaults # to the given geographic coordinate system's authority code, or # to 0 if no geographic coordinate system is known. # [:srs_database] # Optional. If provided, the object should respond to #get and # #clear_cache. If both this and an SRID are # provided, they are used to look up the proj4 and coord_sys # objects from a spatial reference system database. # [:has_z_coordinate] # Support a Z coordinate. Default is false. # Note: this is ignored if a :projection_factory is # provided; in that case, the geographic factory's z-coordinate # availability will match the projection factory's setting. # [:has_m_coordinate] # Support an M coordinate. Default is false. # Note: this is ignored if a :projection_factory is # provided; in that case, the geographic factory's m-coordinate # availability will match the projection factory's setting. # [:wkt_parser] # Configure the parser for WKT. The value is a hash of # configuration parameters for WKRep::WKTParser.new. Default is # the empty hash, indicating the default configuration for # WKRep::WKTParser. # [:wkb_parser] # Configure the parser for WKB. The value is a hash of # configuration parameters for WKRep::WKBParser.new. Default is # the empty hash, indicating the default configuration for # WKRep::WKBParser. # [:wkt_generator] # Configure the generator for WKT. The value is a hash of # configuration parameters for WKRep::WKTGenerator.new. # Default is {:convert_case => :upper}. # [:wkb_generator] # Configure the generator for WKT. The value is a hash of # configuration parameters for WKRep::WKTGenerator.new. # Default is the empty hash, indicating the default configuration # for WKRep::WKBGenerator. # # If a :projection_factory is _not_ provided, you may also # provide options for configuring the projected Cartesian factory. # For example, if GEOS is used for the projected factory, you may # also set the :lenient_multi_polygon_assertions and # :buffer_resolution options. See RGeo::Geos.factory for # more details. def projected_factory(opts = {}) CoordSys.check!(:proj4) db_ = opts[:srs_database] if (projection_factory = opts[:projection_factory]) # Get the projection coordinate systems from the given factory projection_proj4 = projection_factory.proj4 unless projection_proj4 raise ArgumentError, "The :projection_factory does not have a proj4." end projection_coord_sys = projection_factory.coord_sys if projection_coord_sys && !projection_coord_sys.is_a?(CoordSys::CS::ProjectedCoordinateSystem) raise ArgumentError, "The :projection_factory's coord_sys is not a ProjectedCoordinateSystem." end # Determine geographic coordinate system. First check parameters. proj4 = opts[:proj4] coord_sys = opts[:coord_sys] srid = opts[:srid] # Lookup srid from srs database if needed if (!proj4 || !coord_sys) && srid && db_ entry_ = db_.get(srid.to_i) if entry_ proj4 ||= entry_.proj4 coord_sys ||= entry_.coord_sys end end # Fall back to getting the values from the projection. proj4 ||= projection_proj4.get_geographic || _proj_4326 coord_sys ||= projection_coord_sys.geographic_coordinate_system if projection_coord_sys srid ||= coord_sys.authority_code if coord_sys srid ||= 4326 # Now we should have all the coordinate system info. factory = Geographic::Factory.new("Projected", proj4: proj4, coord_sys: coord_sys, srid: srid.to_i, has_z_coordinate: projection_factory.property(:has_z_coordinate), has_m_coordinate: projection_factory.property(:has_m_coordinate), wkt_parser: opts[:wkt_parser], wkt_generator: opts[:wkt_generator], wkb_parser: opts[:wkb_parser], wkb_generator: opts[:wkb_generator]) projector = Geographic::Proj4Projector.create_from_existing_factory(factory, projection_factory) else # Determine projection coordinate system. First check the parameters. projection_proj4 = opts[:projection_proj4] projection_coord_sys = opts[:projection_coord_sys] projection_srid = opts[:projection_srid] # Check the case where we need to look up a srid from an srs database. if (!projection_proj4 || !projection_coord_sys) && projection_srid && db_ entry_ = db_.get(projection_srid.to_i) if entry_ projection_proj4 ||= entry_.proj4 projection_coord_sys ||= entry_.coord_sys end end # A projection proj4 is absolutely required. unless projection_proj4 raise ArgumentError, "Unable to determine the Proj4 for the projected coordinate system." end # Check the projection coordinate systems, and parse if needed. if projection_proj4.is_a?(String) || projection_proj4.is_a?(Hash) actual_projection_proj4 = CoordSys::Proj4.create(projection_proj4) unless actual_projection_proj4 raise ArgumentError, "Bad proj4 syntax: #{projection_proj4.inspect}" end projection_proj4 = actual_projection_proj4 end if projection_coord_sys && !projection_coord_sys.is_a?(CoordSys::CS::ProjectedCoordinateSystem) raise ArgumentError, "The :projection_coord_sys is not a ProjectedCoordinateSystem." end projection_srid ||= projection_coord_sys.authority_code if projection_coord_sys # Determine geographic coordinate system. First check parameters. proj4 = opts[:proj4] coord_sys = opts[:coord_sys] srid = opts[:srid] # Lookup srid from srs database if needed if (!proj4 || !coord_sys) && srid && db_ entry_ = db_.get(srid.to_i) if entry_ proj4 ||= entry_.proj4 coord_sys ||= entry_.coord_sys end end # Fall back to getting the values from the projection. proj4 ||= projection_proj4.get_geographic || _proj_4326 coord_sys ||= projection_coord_sys.geographic_coordinate_system if projection_coord_sys srid ||= coord_sys.authority_code if coord_sys srid ||= 4326 # Now we should have all the coordinate system info. factory = Geographic::Factory.new("Projected", proj4: proj4, coord_sys: coord_sys, srid: srid.to_i, has_z_coordinate: opts[:has_z_coordinate], has_m_coordinate: opts[:has_m_coordinate], wkt_parser: opts[:wkt_parser], wkt_generator: opts[:wkt_generator], wkb_parser: opts[:wkb_parser], wkb_generator: opts[:wkb_generator]) projector = Geographic::Proj4Projector.create_from_proj4(factory, projection_proj4, srid: projection_srid, coord_sys: projection_coord_sys, buffer_resolution: opts[:buffer_resolution], lenient_multi_polygon_assertions: opts[:lenient_multi_polygon_assertions], uses_lenient_assertions: opts[:uses_lenient_assertions], has_z_coordinate: opts[:has_z_coordinate], has_m_coordinate: opts[:has_m_coordinate], wkt_parser: opts[:wkt_parser], wkt_generator: opts[:wkt_generator], wkb_parser: opts[:wkb_parser], wkb_generator: opts[:wkb_generator]) end factory.projector = projector factory end private def proj_4055 unless defined?(@proj44055) @proj44055 = CoordSys.supported?(:proj4) && CoordSys::Proj4.create("+proj=longlat +a=6378137 +b=6378137 +towgs84=0,0,0,0,0,0,0 +no_defs") end @proj44055 end def coord_sys_4055 unless defined?(@coord_sys_4055) @coord_sys_4055 = CoordSys::CS.create_from_wkt('GEOGCS["Popular Visualisation CRS",DATUM["Popular_Visualisation_Datum",SPHEROID["Popular Visualisation Sphere",6378137,0,AUTHORITY["EPSG","7059"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6055"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4055"]]') end @coord_sys_4055 end def proj_4326 unless defined?(@proj_4326) @proj_4326 = CoordSys.supported?(:proj4) && CoordSys::Proj4.create("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs") end @proj_4326 end def coord_sys_4326 unless defined?(@coord_sys_4326) @coord_sys_4326 = CoordSys::CS.create_from_wkt('GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]') end @coord_sys_4326 end end end end