rgeo-dschee 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +29 -0
  3. data/ext/geos_c_impl/coordinates.c +65 -0
  4. data/ext/geos_c_impl/coordinates.h +2 -0
  5. data/ext/geos_c_impl/extconf.rb +43 -0
  6. data/ext/geos_c_impl/factory.c +995 -0
  7. data/ext/geos_c_impl/factory.h +238 -0
  8. data/ext/geos_c_impl/geometry.c +1093 -0
  9. data/ext/geos_c_impl/geometry.h +23 -0
  10. data/ext/geos_c_impl/geometry_collection.c +757 -0
  11. data/ext/geos_c_impl/geometry_collection.h +46 -0
  12. data/ext/geos_c_impl/line_string.c +675 -0
  13. data/ext/geos_c_impl/line_string.h +32 -0
  14. data/ext/geos_c_impl/main.c +40 -0
  15. data/ext/geos_c_impl/point.c +236 -0
  16. data/ext/geos_c_impl/point.h +30 -0
  17. data/ext/geos_c_impl/polygon.c +359 -0
  18. data/ext/geos_c_impl/polygon.h +43 -0
  19. data/ext/geos_c_impl/preface.h +38 -0
  20. data/ext/proj4_c_impl/extconf.rb +62 -0
  21. data/ext/proj4_c_impl/main.c +315 -0
  22. data/lib/rgeo.rb +89 -0
  23. data/lib/rgeo/cartesian.rb +25 -0
  24. data/lib/rgeo/cartesian/analysis.rb +77 -0
  25. data/lib/rgeo/cartesian/bounding_box.rb +398 -0
  26. data/lib/rgeo/cartesian/calculations.rb +113 -0
  27. data/lib/rgeo/cartesian/factory.rb +347 -0
  28. data/lib/rgeo/cartesian/feature_classes.rb +100 -0
  29. data/lib/rgeo/cartesian/feature_methods.rb +88 -0
  30. data/lib/rgeo/cartesian/interface.rb +135 -0
  31. data/lib/rgeo/coord_sys.rb +43 -0
  32. data/lib/rgeo/coord_sys/cs/entities.rb +1315 -0
  33. data/lib/rgeo/coord_sys/cs/factories.rb +148 -0
  34. data/lib/rgeo/coord_sys/cs/wkt_parser.rb +272 -0
  35. data/lib/rgeo/coord_sys/proj4.rb +293 -0
  36. data/lib/rgeo/coord_sys/srs_database/interface.rb +115 -0
  37. data/lib/rgeo/coord_sys/srs_database/proj4_data.rb +140 -0
  38. data/lib/rgeo/coord_sys/srs_database/sr_org.rb +62 -0
  39. data/lib/rgeo/coord_sys/srs_database/url_reader.rb +63 -0
  40. data/lib/rgeo/error.rb +27 -0
  41. data/lib/rgeo/feature.rb +54 -0
  42. data/lib/rgeo/feature/curve.rb +111 -0
  43. data/lib/rgeo/feature/factory.rb +278 -0
  44. data/lib/rgeo/feature/factory_generator.rb +96 -0
  45. data/lib/rgeo/feature/geometry.rb +624 -0
  46. data/lib/rgeo/feature/geometry_collection.rb +95 -0
  47. data/lib/rgeo/feature/line.rb +26 -0
  48. data/lib/rgeo/feature/line_string.rb +60 -0
  49. data/lib/rgeo/feature/linear_ring.rb +26 -0
  50. data/lib/rgeo/feature/mixins.rb +143 -0
  51. data/lib/rgeo/feature/multi_curve.rb +71 -0
  52. data/lib/rgeo/feature/multi_line_string.rb +26 -0
  53. data/lib/rgeo/feature/multi_point.rb +33 -0
  54. data/lib/rgeo/feature/multi_polygon.rb +57 -0
  55. data/lib/rgeo/feature/multi_surface.rb +73 -0
  56. data/lib/rgeo/feature/point.rb +84 -0
  57. data/lib/rgeo/feature/polygon.rb +97 -0
  58. data/lib/rgeo/feature/surface.rb +79 -0
  59. data/lib/rgeo/feature/types.rb +284 -0
  60. data/lib/rgeo/geographic.rb +40 -0
  61. data/lib/rgeo/geographic/factory.rb +450 -0
  62. data/lib/rgeo/geographic/interface.rb +489 -0
  63. data/lib/rgeo/geographic/proj4_projector.rb +58 -0
  64. data/lib/rgeo/geographic/projected_feature_classes.rb +107 -0
  65. data/lib/rgeo/geographic/projected_feature_methods.rb +212 -0
  66. data/lib/rgeo/geographic/projected_window.rb +383 -0
  67. data/lib/rgeo/geographic/simple_mercator_projector.rb +110 -0
  68. data/lib/rgeo/geographic/spherical_feature_classes.rb +100 -0
  69. data/lib/rgeo/geographic/spherical_feature_methods.rb +134 -0
  70. data/lib/rgeo/geographic/spherical_math.rb +188 -0
  71. data/lib/rgeo/geos.rb +89 -0
  72. data/lib/rgeo/geos/capi_factory.rb +470 -0
  73. data/lib/rgeo/geos/capi_feature_classes.rb +129 -0
  74. data/lib/rgeo/geos/ffi_factory.rb +592 -0
  75. data/lib/rgeo/geos/ffi_feature_classes.rb +83 -0
  76. data/lib/rgeo/geos/ffi_feature_methods.rb +574 -0
  77. data/lib/rgeo/geos/interface.rb +202 -0
  78. data/lib/rgeo/geos/utils.rb +74 -0
  79. data/lib/rgeo/geos/zm_factory.rb +405 -0
  80. data/lib/rgeo/geos/zm_feature_classes.rb +80 -0
  81. data/lib/rgeo/geos/zm_feature_methods.rb +344 -0
  82. data/lib/rgeo/impl_helper.rb +19 -0
  83. data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +185 -0
  84. data/lib/rgeo/impl_helper/basic_geometry_methods.rb +61 -0
  85. data/lib/rgeo/impl_helper/basic_line_string_methods.rb +146 -0
  86. data/lib/rgeo/impl_helper/basic_point_methods.rb +104 -0
  87. data/lib/rgeo/impl_helper/basic_polygon_methods.rb +87 -0
  88. data/lib/rgeo/impl_helper/math.rb +14 -0
  89. data/lib/rgeo/impl_helper/utils.rb +29 -0
  90. data/lib/rgeo/version.rb +3 -0
  91. data/lib/rgeo/wkrep.rb +37 -0
  92. data/lib/rgeo/wkrep/wkb_generator.rb +201 -0
  93. data/lib/rgeo/wkrep/wkb_parser.rb +251 -0
  94. data/lib/rgeo/wkrep/wkt_generator.rb +207 -0
  95. data/lib/rgeo/wkrep/wkt_parser.rb +415 -0
  96. data/lib/rgeo/yaml.rb +23 -0
  97. data/test/cartesian_analysis_test.rb +65 -0
  98. data/test/cartesian_bbox_test.rb +123 -0
  99. data/test/common/factory_tests.rb +78 -0
  100. data/test/common/geometry_collection_tests.rb +237 -0
  101. data/test/common/line_string_tests.rb +330 -0
  102. data/test/common/multi_line_string_tests.rb +182 -0
  103. data/test/common/multi_point_tests.rb +200 -0
  104. data/test/common/multi_polygon_tests.rb +191 -0
  105. data/test/common/point_tests.rb +370 -0
  106. data/test/common/polygon_tests.rb +261 -0
  107. data/test/coord_sys/ogc_cs_test.rb +342 -0
  108. data/test/coord_sys/proj4_srs_data_test.rb +41 -0
  109. data/test/coord_sys/proj4_test.rb +150 -0
  110. data/test/coord_sys/sr_org_test.rb +32 -0
  111. data/test/coord_sys/url_reader_test.rb +42 -0
  112. data/test/geos_capi/factory_test.rb +31 -0
  113. data/test/geos_capi/geometry_collection_test.rb +24 -0
  114. data/test/geos_capi/line_string_test.rb +24 -0
  115. data/test/geos_capi/misc_test.rb +116 -0
  116. data/test/geos_capi/multi_line_string_test.rb +24 -0
  117. data/test/geos_capi/multi_point_test.rb +24 -0
  118. data/test/geos_capi/multi_polygon_test.rb +39 -0
  119. data/test/geos_capi/parsing_unparsing_test.rb +40 -0
  120. data/test/geos_capi/point_test.rb +72 -0
  121. data/test/geos_capi/polygon_test.rb +154 -0
  122. data/test/geos_capi/zmfactory_test.rb +57 -0
  123. data/test/geos_ffi/factory_test.rb +31 -0
  124. data/test/geos_ffi/geometry_collection_test.rb +24 -0
  125. data/test/geos_ffi/line_string_test.rb +24 -0
  126. data/test/geos_ffi/misc_test.rb +63 -0
  127. data/test/geos_ffi/multi_line_string_test.rb +24 -0
  128. data/test/geos_ffi/multi_point_test.rb +24 -0
  129. data/test/geos_ffi/multi_polygon_test.rb +33 -0
  130. data/test/geos_ffi/parsing_unparsing_test.rb +41 -0
  131. data/test/geos_ffi/point_test.rb +77 -0
  132. data/test/geos_ffi/polygon_test.rb +46 -0
  133. data/test/geos_ffi/zmfactory_test.rb +58 -0
  134. data/test/mixins_test.rb +141 -0
  135. data/test/oneoff_test.rb +26 -0
  136. data/test/projected_geographic/factory_test.rb +25 -0
  137. data/test/projected_geographic/geometry_collection_test.rb +24 -0
  138. data/test/projected_geographic/line_string_test.rb +24 -0
  139. data/test/projected_geographic/multi_line_string_test.rb +26 -0
  140. data/test/projected_geographic/multi_point_test.rb +30 -0
  141. data/test/projected_geographic/multi_polygon_test.rb +25 -0
  142. data/test/projected_geographic/point_test.rb +51 -0
  143. data/test/projected_geographic/polygon_test.rb +24 -0
  144. data/test/simple_cartesian/calculations_test.rb +99 -0
  145. data/test/simple_cartesian/factory_test.rb +27 -0
  146. data/test/simple_cartesian/geometry_collection_test.rb +30 -0
  147. data/test/simple_cartesian/line_string_test.rb +31 -0
  148. data/test/simple_cartesian/multi_line_string_test.rb +28 -0
  149. data/test/simple_cartesian/multi_point_test.rb +31 -0
  150. data/test/simple_cartesian/multi_polygon_test.rb +31 -0
  151. data/test/simple_cartesian/point_test.rb +50 -0
  152. data/test/simple_cartesian/polygon_test.rb +28 -0
  153. data/test/simple_mercator/factory_test.rb +25 -0
  154. data/test/simple_mercator/geometry_collection_test.rb +24 -0
  155. data/test/simple_mercator/line_string_test.rb +24 -0
  156. data/test/simple_mercator/multi_line_string_test.rb +26 -0
  157. data/test/simple_mercator/multi_point_test.rb +29 -0
  158. data/test/simple_mercator/multi_polygon_test.rb +25 -0
  159. data/test/simple_mercator/point_test.rb +55 -0
  160. data/test/simple_mercator/polygon_test.rb +24 -0
  161. data/test/simple_mercator/window_test.rb +173 -0
  162. data/test/spherical_geographic/calculations_test.rb +167 -0
  163. data/test/spherical_geographic/factory_test.rb +27 -0
  164. data/test/spherical_geographic/geometry_collection_test.rb +31 -0
  165. data/test/spherical_geographic/line_string_test.rb +31 -0
  166. data/test/spherical_geographic/multi_line_string_test.rb +29 -0
  167. data/test/spherical_geographic/multi_point_test.rb +31 -0
  168. data/test/spherical_geographic/multi_polygon_test.rb +31 -0
  169. data/test/spherical_geographic/point_test.rb +78 -0
  170. data/test/spherical_geographic/polygon_test.rb +28 -0
  171. data/test/types_test.rb +42 -0
  172. data/test/wkrep/wkb_generator_test.rb +185 -0
  173. data/test/wkrep/wkb_parser_test.rb +293 -0
  174. data/test/wkrep/wkt_generator_test.rb +294 -0
  175. data/test/wkrep/wkt_parser_test.rb +412 -0
  176. metadata +386 -0
data/lib/rgeo.rb ADDED
@@ -0,0 +1,89 @@
1
+ # RGeo is a spatial data library for Ruby. It focuses on the storage and
2
+ # manipulation of spatial data types such as points, lines, and polygons.
3
+ #
4
+ # RGeo comprises a number of modules. The "rgeo" gem provides a suite of
5
+ # standard modules. Additional optional modules are provided by separate
6
+ # gems with names of the form "<tt>rgeo-*</tt>".
7
+ #
8
+ # === Standard modules
9
+ #
10
+ # These are the standard modules provided by the "rgeo" gem.
11
+ #
12
+ # * RGeo::Feature contains interface specifications for spatial
13
+ # objects implemented by RGeo. These interfaces closely follow the OGC
14
+ # Simple Features Specifiation (SFS). This module forms the core of RGeo.
15
+ #
16
+ # * RGeo::CoordSys contains classes for representing spatial
17
+ # reference systems and coordinate transformations. For example, it
18
+ # includes a wrapper for the Proj4 library, supporting many geographic
19
+ # projections.
20
+ #
21
+ # * RGeo::Cartesian is a gateway for geometric data implementations
22
+ # that operate in Caresian (flat) coordinate systems. It also provides a
23
+ # basic pure ruby Cartesian implementation. This implementation does not
24
+ # cover all the geometric analysis operations defined by the SFS, but it
25
+ # does not require an external C library and is often sufficient for
26
+ # basic applications.
27
+ #
28
+ # * RGeo::Geos is another Cartesian implementation that wraps the
29
+ # GEOS library to provide a full, high-performance implementation of
30
+ # Cartesian geometry that includes every operation defined in the SFS.
31
+ # It requires GEOS 3.2 or later.
32
+ #
33
+ # * RGeo::Geographic contains spatial implementations that operate
34
+ # in latitude-longitude coordinates and are well-suited for geographic
35
+ # location based applications. Geographic spatial objects may also be
36
+ # linked to projections.
37
+ #
38
+ # * RGeo::WKRep contains tools for reading and writing spatial
39
+ # data in the OGC Well-Known Text (WKT) and Well-Known Binary (WKB)
40
+ # representations. It also supports common variants such as the PostGIS
41
+ # EWKT and EWKB representations.
42
+ #
43
+ # === Optional Modules
44
+ #
45
+ # Here is a partial list of optional modules available as separate gems.
46
+ #
47
+ # * <b>rgeo-geojson</b> provides the RGeo::GeoJSON module, containing
48
+ # tools for GeoJSON encoding and decoding of spatial objects.
49
+ #
50
+ # * <b>rgeo-shapefile</b> provides the RGeo::Shapefile module, containing
51
+ # tools for reading ESRI shapefiles.
52
+ #
53
+ # * <b>rgeo-activerecord</b> provides the RGeo::ActiveRecord module,
54
+ # containing some ActiveRecord extensions for spatial databases, and a
55
+ # set of common tools for ActiveRecord spatial database adapters.
56
+ #
57
+ # Several ActiveRecord adapters use RGeo. These include:
58
+ #
59
+ # * <b>mysqlspatial</b>, an adapter for MySQL spatial extensions based on
60
+ # the mysql adapter. Available as the activerecord-mysqlspatial-adapter
61
+ # gem. <i>This adapter is deprecated due to bugs in the legacy mysql
62
+ # gem. You should use the mysql2spatial adapter instead.</i>
63
+ #
64
+ # * <b>mysql2spatial</b>, an adapter for MySQL spatial extensions based on
65
+ # the mysql2 adapter. Available as the activerecord-mysql2spatial-adapter
66
+ # gem.
67
+ #
68
+ # * <b>spatialite</b>, an adapter for the SpatiaLite extension to the
69
+ # Sqlite3 database, and based on the sqlite3 adapter. Available as the
70
+ # activerecord-spatialite-adapter gem.
71
+ #
72
+ # * <b>postgis</b>, an adapter for the PostGIS extension to the PostgreSQL
73
+ # database, and based on the postgresql adapter. Available as the
74
+ # activerecord-postgis-adapter gem.
75
+
76
+ module RGeo
77
+ end
78
+
79
+ # Core modules
80
+ require "rgeo/yaml"
81
+ require "rgeo/version"
82
+ require "rgeo/error"
83
+ require "rgeo/feature"
84
+ require "rgeo/coord_sys"
85
+ require "rgeo/impl_helper"
86
+ require "rgeo/wkrep"
87
+ require "rgeo/geos"
88
+ require "rgeo/cartesian"
89
+ require "rgeo/geographic"
@@ -0,0 +1,25 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Cartesian features for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ module RGeo
8
+ # The Cartesian module is a gateway to implementations that use the
9
+ # Cartesian (i.e. flat) coordinate system. It provides convenient
10
+ # access to Cartesian factories such as the Geos implementation and
11
+ # the simple Cartesian implementation. It also provides a namespace
12
+ # for Cartesian-specific analysis tools.
13
+
14
+ module Cartesian
15
+ end
16
+ end
17
+
18
+ # Implementation files.
19
+ require "rgeo/cartesian/calculations"
20
+ require "rgeo/cartesian/feature_methods"
21
+ require "rgeo/cartesian/feature_classes"
22
+ require "rgeo/cartesian/factory"
23
+ require "rgeo/cartesian/interface"
24
+ require "rgeo/cartesian/bounding_box"
25
+ require "rgeo/cartesian/analysis"
@@ -0,0 +1,77 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Cartesian geometric analysis utilities
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ module RGeo
8
+ module Cartesian
9
+ # This provides includes some spatial analysis algorithms supporting
10
+ # Cartesian data.
11
+
12
+ module Analysis
13
+ class << self
14
+ # Given a LineString, which must be a ring, determine whether the
15
+ # ring proceeds clockwise or counterclockwise.
16
+ # Returns 1 for counterclockwise, or -1 for clockwise.
17
+ #
18
+ # Returns 0 if the ring is empty.
19
+ # The return value is undefined if the object is not a ring, or
20
+ # is not in a Cartesian coordinate system.
21
+
22
+ def ring_direction(ring_)
23
+ size_ = ring_.num_points - 1
24
+ return 0 if size_ == 0
25
+
26
+ # Extract unit-length segments from the ring.
27
+ segs_ = []
28
+ size_.times do |i_|
29
+ p0_ = ring_.point_n(i_)
30
+ p1_ = ring_.point_n(i_ + 1)
31
+ x_ = p1_.x - p0_.x
32
+ y_ = p1_.y - p0_.y
33
+ r_ = ::Math.sqrt(x_ * x_ + y_ * y_)
34
+ if r_ > 0.0
35
+ segs_ << x_ / r_ << y_ / r_
36
+ else
37
+ size_ -= 1
38
+ end
39
+ end
40
+ segs_ << segs_[0] << segs_[1]
41
+
42
+ # Extract angles from the segments by subtracting the segments.
43
+ # Note angles are represented as cos/sin pairs so we don't
44
+ # have to calculate any trig functions.
45
+ angs_ = []
46
+ size_.times do |i_|
47
+ x0_, y0_, x1_, y1_ = segs_[i_ * 2, 4]
48
+ angs_ << x0_ * x1_ + y0_ * y1_ << x0_ * y1_ - x1_ * y0_
49
+ end
50
+
51
+ # Now add the angles and count revolutions.
52
+ # Again, our running sum is represented as a cos/sin pair.
53
+ revolutions_ = 0
54
+ sin_ = 0.0
55
+ cos_ = 1.0
56
+ angs_.each_slice(2) do |(x_, y_)|
57
+ ready_ = y_ > 0.0 && sin_ > 0.0 || y_ < 0.0 && sin_ < 0.0
58
+ if y_ != 0.0
59
+ s_ = sin_ * x_ + cos_ * y_
60
+ c_ = cos_ * x_ - sin_ * y_
61
+ r_ = ::Math.sqrt(s_ * s_ + c_ * c_)
62
+ sin_ = s_ / r_
63
+ cos_ = c_ / r_
64
+ end
65
+ next unless ready_
66
+ if y_ > 0.0 && sin_ <= 0.0
67
+ revolutions_ += 1
68
+ elsif y_ < 0.0 && sin_ >= 0.0
69
+ revolutions_ -= 1
70
+ end
71
+ end
72
+ revolutions_
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,398 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Cartesian bounding box
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ module RGeo
8
+ module Cartesian
9
+ # This is a bounding box for Cartesian data.
10
+ # The simple cartesian implementation uses this internally to compute
11
+ # envelopes. You may also use it directly to compute and represent
12
+ # bounding boxes.
13
+ #
14
+ # A bounding box is a set of ranges in each dimension: X, Y, as well
15
+ # as Z and M if supported. You can compute a bounding box for one or
16
+ # more geometry objects by creating a new bounding box object, and
17
+ # adding the geometries to it. You may then query it for the bounds,
18
+ # or use it to determine whether it encloses other geometries or
19
+ # bounding boxes.
20
+
21
+ class BoundingBox
22
+ # Create a bounding box given two corner points.
23
+ # The bounding box will be given the factory of the first point.
24
+ # You may also provide the same options available to
25
+ # BoundingBox.new.
26
+
27
+ def self.create_from_points(point1_, point2_, opts_ = {})
28
+ factory_ = point1_.factory
29
+ new(factory_, opts_)._add_geometry(point1_).add(point2_)
30
+ end
31
+
32
+ # Create a bounding box given a geometry to surround.
33
+ # The bounding box will be given the factory of the geometry.
34
+ # You may also provide the same options available to
35
+ # BoundingBox.new.
36
+
37
+ def self.create_from_geometry(geom_, opts_ = {})
38
+ factory_ = geom_.factory
39
+ new(factory_, opts_)._add_geometry(geom_)
40
+ end
41
+
42
+ # Create a new empty bounding box with the given factory.
43
+ #
44
+ # The factory defines the coordinate system for the bounding box,
45
+ # and also defines whether it should track Z and M coordinates.
46
+ # All geometries will be cast to this factory when added to this
47
+ # bounding box, and any generated envelope geometry will have this
48
+ # as its factory.
49
+ #
50
+ # Options include:
51
+ #
52
+ # [<tt>:ignore_z</tt>]
53
+ # If true, ignore z coordinates even if the factory supports them.
54
+ # Default is false.
55
+ # [<tt>:ignore_m</tt>]
56
+ # If true, ignore m coordinates even if the factory supports them.
57
+ # Default is false.
58
+
59
+ def initialize(factory_, opts_ = {})
60
+ @factory = factory_
61
+ if (values_ = opts_[:raw])
62
+ @has_z, @has_m, @min_x, @max_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m = values_
63
+ else
64
+ @has_z = !opts_[:ignore_z] && factory_.property(:has_z_coordinate) ? true : false
65
+ @has_m = !opts_[:ignore_m] && factory_.property(:has_m_coordinate) ? true : false
66
+ @min_x = @max_x = @min_y = @max_y = @min_z = @max_z = @min_m = @max_m = nil
67
+ end
68
+ end
69
+
70
+ def eql?(rhs_) # :nodoc:
71
+ rhs_.is_a?(BoundingBox) && @factory == rhs_.factory &&
72
+ @min_x == rhs_.min_x && @max_x == rhs_.max_x &&
73
+ @min_y == rhs_.min_y && @max_y == rhs_.max_y &&
74
+ @min_z == rhs_.min_z && @max_z == rhs_.max_z &&
75
+ @min_m == rhs_.min_m && @max_m == rhs_.max_m
76
+ end
77
+ alias_method :==, :eql?
78
+
79
+ # Returns the bounding box's factory.
80
+
81
+ attr_reader :factory
82
+
83
+ # Returns true if this bounding box is still empty.
84
+
85
+ def empty?
86
+ @min_x.nil?
87
+ end
88
+
89
+ # Returns true if this bounding box is degenerate. That is,
90
+ # it is nonempty but contains only a single point because both
91
+ # the X and Y spans are 0. Infinitesimal boxes are also
92
+ # always degenerate.
93
+
94
+ def infinitesimal?
95
+ @min_x && @min_x == @max_x && @min_y == @max_y
96
+ end
97
+
98
+ # Returns true if this bounding box is degenerate. That is,
99
+ # it is nonempty but has zero area because either or both
100
+ # of the X or Y spans are 0.
101
+
102
+ def degenerate?
103
+ @min_x && (@min_x == @max_x || @min_y == @max_y)
104
+ end
105
+
106
+ # Returns true if this bounding box tracks Z coordinates.
107
+
108
+ attr_reader :has_z
109
+
110
+ # Returns true if this bounding box tracks M coordinates.
111
+
112
+ attr_reader :has_m
113
+
114
+ # Returns the minimum X, or nil if this bounding box is empty.
115
+
116
+ attr_reader :min_x
117
+
118
+ # Returns the maximum X, or nil if this bounding box is empty.
119
+
120
+ attr_reader :max_x
121
+
122
+ # Returns the midpoint X, or nil if this bounding box is empty.
123
+
124
+ def center_x
125
+ @max_x ? (@max_x + @min_x) * 0.5 : nil
126
+ end
127
+
128
+ # Returns the X span, or 0 if this bounding box is empty.
129
+
130
+ def x_span
131
+ @max_x ? @max_x - @min_x : 0
132
+ end
133
+
134
+ # Returns the minimum Y, or nil if this bounding box is empty.
135
+
136
+ attr_reader :min_y
137
+
138
+ # Returns the maximum Y, or nil if this bounding box is empty.
139
+
140
+ attr_reader :max_y
141
+
142
+ # Returns the midpoint Y, or nil if this bounding box is empty.
143
+
144
+ def center_y
145
+ @max_y ? (@max_y + @min_y) * 0.5 : nil
146
+ end
147
+
148
+ # Returns the Y span, or 0 if this bounding box is empty.
149
+
150
+ def y_span
151
+ @max_y ? @max_y - @min_y : 0
152
+ end
153
+
154
+ # Returns the minimum Z, or nil if this bounding box is empty.
155
+
156
+ attr_reader :min_z
157
+
158
+ # Returns the maximum Z, or nil if this bounding box is empty.
159
+
160
+ attr_reader :max_z
161
+
162
+ # Returns the midpoint Z, or nil if this bounding box is empty or has no Z.
163
+
164
+ def center_z
165
+ @max_z ? (@max_z + @min_z) * 0.5 : nil
166
+ end
167
+
168
+ # Returns the Z span, 0 if this bounding box is empty, or nil if it has no Z.
169
+
170
+ def z_span
171
+ @has_z ? (@max_z ? @max_z - @min_z : 0) : nil
172
+ end
173
+
174
+ # Returns the minimum M, or nil if this bounding box is empty.
175
+
176
+ attr_reader :min_m
177
+
178
+ # Returns the maximum M, or nil if this bounding box is empty.
179
+
180
+ attr_reader :max_m
181
+
182
+ # Returns the midpoint M, or nil if this bounding box is empty or has no M.
183
+
184
+ def center_m
185
+ @max_m ? (@max_m + @min_m) * 0.5 : nil
186
+ end
187
+
188
+ # Returns the M span, 0 if this bounding box is empty, or nil if it has no M.
189
+
190
+ def m_span
191
+ @has_m ? (@max_m ? @max_m - @min_m : 0) : nil
192
+ end
193
+
194
+ # Returns a point representing the minimum extent in all dimensions,
195
+ # or nil if this bounding box is empty.
196
+
197
+ def min_point
198
+ if @min_x
199
+ extras_ = []
200
+ extras_ << @min_z if @has_z
201
+ extras_ << @min_m if @has_m
202
+ @factory.point(@min_x, @min_y, *extras_)
203
+ end
204
+ end
205
+
206
+ # Returns a point representing the maximum extent in all dimensions,
207
+ # or nil if this bounding box is empty.
208
+
209
+ def max_point
210
+ if @min_x
211
+ extras_ = []
212
+ extras_ << @max_z if @has_z
213
+ extras_ << @max_m if @has_m
214
+ @factory.point(@max_x, @max_y, *extras_)
215
+ end
216
+ end
217
+
218
+ # Adjusts the extents of this bounding box to encompass the given
219
+ # object, which may be a geometry or another bounding box.
220
+ # Returns self.
221
+
222
+ def add(geometry_)
223
+ case geometry_
224
+ when BoundingBox
225
+ add(geometry_.min_point)
226
+ add(geometry_.max_point)
227
+ when Feature::Geometry
228
+ if geometry_.factory == @factory
229
+ _add_geometry(geometry_)
230
+ else
231
+ _add_geometry(Feature.cast(geometry_, @factory))
232
+ end
233
+ end
234
+ self
235
+ end
236
+
237
+ # Converts this bounding box to an envelope, which will be the
238
+ # empty collection (if the bounding box is empty), a point (if the
239
+ # bounding box is not empty but both spans are 0), a line (if only
240
+ # one of the two spans is 0) or a polygon (if neither span is 0).
241
+
242
+ def to_geometry
243
+ if @min_x
244
+ extras_ = []
245
+ extras_ << @min_z if @has_z
246
+ extras_ << @min_m if @has_m
247
+ point_min_ = @factory.point(@min_x, @min_y, *extras_)
248
+ if infinitesimal?
249
+ point_min_
250
+ else
251
+ extras_ = []
252
+ extras_ << @max_z if @has_z
253
+ extras_ << @max_m if @has_m
254
+ point_max_ = @factory.point(@max_x, @max_y, *extras_)
255
+ if degenerate?
256
+ @factory.line(point_min_, point_max_)
257
+ else
258
+ @factory.polygon(@factory.linear_ring([point_min_,
259
+ @factory.point(@max_x, @min_y, *extras_), point_max_,
260
+ @factory.point(@min_x, @max_y, *extras_), point_min_]))
261
+ end
262
+ end
263
+ else
264
+ @factory.collection([])
265
+ end
266
+ end
267
+
268
+ # Returns true if this bounding box contains the given object,
269
+ # which may be a geometry or another bounding box.
270
+ #
271
+ # Supports these options:
272
+ #
273
+ # [<tt>:ignore_z</tt>]
274
+ # Ignore the Z coordinate when testing, even if both objects
275
+ # have Z. Default is false.
276
+ # [<tt>:ignore_m</tt>]
277
+ # Ignore the M coordinate when testing, even if both objects
278
+ # have M. Default is false.
279
+
280
+ def contains?(rhs_, opts_ = {})
281
+ if Feature::Geometry === rhs_
282
+ contains?(BoundingBox.new(@factory).add(rhs_))
283
+ elsif rhs_.empty?
284
+ true
285
+ elsif empty?
286
+ false
287
+ elsif @min_x > rhs_.min_x || @max_x < rhs_.max_x || @min_y > rhs_.min_y || @max_y < rhs_.max_y
288
+ false
289
+ elsif @has_m && rhs_.has_m && !opts_[:ignore_m] && (@min_m > rhs_.min_m || @max_m < rhs_.max_m)
290
+ false
291
+ elsif @has_z && rhs_.has_z && !opts_[:ignore_z] && (@min_z > rhs_.min_z || @max_z < rhs_.max_z)
292
+ false
293
+ else
294
+ true
295
+ end
296
+ end
297
+
298
+ # Returns this bounding box subdivided, as an array of bounding boxes.
299
+ # If this bounding box is empty, returns the empty array.
300
+ # If this bounding box is a point, returns a one-element array
301
+ # containing the current point.
302
+ # If the x or y span is 0, bisects the line.
303
+ # Otherwise, generally returns a 4-1 subdivision in the X-Y plane.
304
+ # Does not subdivide on Z or M.
305
+ #
306
+ # [<tt>:bisect_factor</tt>]
307
+ # An optional floating point value that should be greater than 1.0.
308
+ # If the ratio between the larger span and the smaller span is
309
+ # greater than this factor, the bounding box is divided only in
310
+ # half instead of fourths.
311
+
312
+ def subdivide(opts_ = {})
313
+ return [] if empty?
314
+ if infinitesimal?
315
+ return [
316
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
317
+ @min_x, @max_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m])
318
+ ]
319
+ end
320
+ factor_ = opts_[:bisect_factor]
321
+ factor_ ||= 1 if degenerate?
322
+ if factor_
323
+ if x_span > y_span * factor_
324
+ return [
325
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
326
+ @min_x, center_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m]),
327
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
328
+ center_x, @max_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m])
329
+ ]
330
+ elsif y_span > x_span * factor_
331
+ return [
332
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
333
+ @min_x, @max_x, @min_y, center_y, @min_z, @max_z, @min_m, @max_m]),
334
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
335
+ @min_x, @max_x, center_y, @max_y, @min_z, @max_z, @min_m, @max_m])
336
+ ]
337
+ end
338
+ end
339
+ [
340
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
341
+ @min_x, center_x, @min_y, center_y, @min_z, @max_z, @min_m, @max_m]),
342
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
343
+ center_x, @max_x, @min_y, center_y, @min_z, @max_z, @min_m, @max_m]),
344
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
345
+ @min_x, center_x, center_y, @max_y, @min_z, @max_z, @min_m, @max_m]),
346
+ BoundingBox.new(@factory, raw: [@has_z, @has_m,
347
+ center_x, @max_x, center_y, @max_y, @min_z, @max_z, @min_m, @max_m])
348
+ ]
349
+ end
350
+
351
+ def _add_geometry(geometry_) # :nodoc:
352
+ case geometry_
353
+ when Feature::Point
354
+ _add_point(geometry_)
355
+ when Feature::LineString
356
+ geometry_.points.each { |p_| _add_point(p_) }
357
+ when Feature::Polygon
358
+ geometry_.exterior_ring.points.each { |p_| _add_point(p_) }
359
+ when Feature::MultiPoint
360
+ geometry_.each { |p_| _add_point(p_) }
361
+ when Feature::MultiLineString
362
+ geometry_.each { |line_| line_.points.each { |p_| _add_point(p_) } }
363
+ when Feature::MultiPolygon
364
+ geometry_.each { |poly_| poly_.exterior_ring.points.each { |p_| _add_point(p_) } }
365
+ when Feature::GeometryCollection
366
+ geometry_.each { |g_| _add_geometry(g_) }
367
+ end
368
+ self
369
+ end
370
+
371
+ def _add_point(point_) # :nodoc:
372
+ if @min_x
373
+ x_ = point_.x
374
+ @min_x = x_ if x_ < @min_x
375
+ @max_x = x_ if x_ > @max_x
376
+ y_ = point_.y
377
+ @min_y = y_ if y_ < @min_y
378
+ @max_y = y_ if y_ > @max_y
379
+ if @has_z
380
+ z_ = point_.z
381
+ @min_z = z_ if z_ < @min_z
382
+ @max_z = z_ if z_ > @max_z
383
+ end
384
+ if @has_m
385
+ m_ = point_.m
386
+ @min_m = m_ if m_ < @min_m
387
+ @max_m = m_ if m_ > @max_m
388
+ end
389
+ else
390
+ @min_x = @max_x = point_.x
391
+ @min_y = @max_y = point_.y
392
+ @min_z = @max_z = point_.z if @has_z
393
+ @min_m = @max_m = point_.m if @has_m
394
+ end
395
+ end
396
+ end
397
+ end
398
+ end