geos-extensions 0.0.7 → 0.1.0

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.
data/README.rdoc CHANGED
@@ -31,7 +31,7 @@ ZGEL contains a number of enhancements to the GEOS Ruby library:
31
31
  JavaScript output formats that we use for our applications. There are
32
32
  also similar methods for outputting to WKT and WKB such as
33
33
  Geos::Geometry#to_wkt, #to_kml, #to_georss and a number of methods to
34
- output to Google Maps API v2-style JavaScript.
34
+ output to Google Maps API-style JavaScript.
35
35
 
36
36
  * a bunch of helper methods to quickly grab some information from
37
37
  geometries like Geos::Point#lat and Geos::Point#lng.
@@ -83,6 +83,30 @@ We've also included some Rails integration for PostGIS, including:
83
83
  * st_within
84
84
  * st_dwithin
85
85
 
86
+ Ordering scopes are also included:
87
+
88
+ * order_by_ndims
89
+ * order_by_npoints
90
+ * order_by_nrings
91
+ * order_by_numgeometries
92
+ * order_by_numinteriorring
93
+ * order_by_numinteriorrings
94
+ * order_by_numpoints
95
+ * order_by_length3d
96
+ * order_by_length
97
+ * order_by_length2d
98
+ * order_by_perimeter
99
+ * order_by_perimeter2d
100
+ * order_by_perimeter3d
101
+ * order_by_distance
102
+ * order_by_distance_sphere
103
+ * order_by_maxdistance
104
+ * order_by_hausdorffdistance
105
+ * order_by_distance_spheroid
106
+ * order_by_length2d_spheroid
107
+ * order_by_length3d_spheroid
108
+ * order_by_length_spheroid
109
+
86
110
  These let you chain together scopes to build geospatial queries:
87
111
 
88
112
  neighbourhood = Neighbourhood.find(12345)
@@ -94,6 +118,30 @@ We've also included some Rails integration for PostGIS, including:
94
118
  :limit => 10
95
119
  )
96
120
 
121
+ === Google Maps API Output
122
+
123
+ Starting with version 0.1.0, ZGEL supports both Google Maps API
124
+ version 2 and version 3 style outputs. By default and for the sake of
125
+ backwards compatibility, API version 2 output will remain the default
126
+ but as Google has deprecated API version 2, so shall we at some point
127
+ in the future. To switch between API versions, use the
128
+ Geos::GoogleMaps.use_api(version) method:
129
+
130
+ g = Geos.read('point(0 0)')
131
+ Geos::GoogleMaps.use_api(2)
132
+ puts g.to_g_marker
133
+ Geos::GoogleMaps.use_api(3)
134
+ puts g.to_g_marker
135
+
136
+ Outputs
137
+
138
+ new google.maps.Marker(new google.maps.LatLng(0.0, 0.0), {})
139
+ new google.maps.Marker({"position": new google.maps.LatLng(0.0, 0.0)})
140
+
141
+ At an unspecified point in the future, we'll likely make Google Maps
142
+ API version 3 the default, but for the time being, we'll stick with
143
+ version 2 since switching between the two is pretty painless.
144
+
97
145
  === Running ActiveRecord Tests
98
146
 
99
147
  ActiveRecord unit tests can be run by setting the TEST_ACTIVERECORD
@@ -105,4 +153,3 @@ to the directory the files are in to create the database properly. You
105
153
  can also set the database up manually before running the tests -- just
106
154
  make sure the database has PostGIS installed with the spatial_ref_sys
107
155
  table populated and you'll be fine.
108
-
data/Rakefile CHANGED
@@ -2,17 +2,18 @@
2
2
  # -*- ruby -*-
3
3
 
4
4
  require 'rubygems'
5
- require 'rake/gempackagetask'
5
+ require 'rubygems/package_task'
6
6
  require 'rake/testtask'
7
- require 'rake/rdoctask'
7
+ require 'rdoc/task'
8
8
 
9
9
  $:.push 'lib'
10
10
 
11
+ version = File.read('VERSION') rescue ''
12
+
11
13
  begin
12
14
  require 'jeweler'
13
15
  Jeweler::Tasks.new do |gem|
14
16
  gem.name = "geos-extensions"
15
- gem.version = "0.0.7"
16
17
  gem.summary = "Extensions for the GEOS library."
17
18
  gem.description = gem.summary
18
19
  gem.email = "code@zoocasa.com"
@@ -32,8 +33,9 @@ end
32
33
 
33
34
  desc 'Build docs'
34
35
  Rake::RDocTask.new do |t|
35
- require 'rdoc/rdoc'
36
+ require 'rdoc'
36
37
  t.main = 'README.rdoc'
38
+ t.title = "Geos Extensions #{version}"
37
39
  t.rdoc_dir = 'doc'
38
40
  t.rdoc_files.include('README.rdoc', 'MIT-LICENSE', 'lib/**/*.rb')
39
41
  end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,39 @@
1
+
2
+ module Geos
3
+ class GeometryColumn < ::ActiveRecord::Base
4
+ set_table_name 'geometry_columns'
5
+ set_inheritance_column 'nonexistent_column_name_type'
6
+
7
+ belongs_to :spatial_ref_sys,
8
+ :foreign_key => :srid,
9
+ :inverse_of => :geometry_columns
10
+
11
+ after_initialize proc { |row|
12
+ row.f_table_catalog ||= ''
13
+ }
14
+
15
+ validates :f_table_catalog,
16
+ :length => {
17
+ :minimum => 0
18
+ }
19
+
20
+ validates :f_table_schema,
21
+ :presence => true
22
+
23
+ validates :f_table_name,
24
+ :presence => true
25
+
26
+ validates :f_geometry_column,
27
+ :presence => true
28
+
29
+ validates :coord_dimension,
30
+ :presence => true
31
+
32
+ validates :srid,
33
+ :presence => true
34
+
35
+ validates :type,
36
+ :presence => true
37
+ end
38
+ end
39
+
@@ -0,0 +1,12 @@
1
+
2
+ module Geos
3
+ class SpatialRefSys < ::ActiveRecord::Base
4
+ set_table_name 'spatial_ref_sys'
5
+ set_primary_key 'srid'
6
+
7
+ has_many :geometry_columns,
8
+ :foreign_key => :srid,
9
+ :inverse_of => :spatial_ref_sys
10
+ end
11
+ end
12
+
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{geos-extensions}
8
- s.version = "0.0.7"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["J Smith"]
12
- s.date = %q{2011-03-21}
12
+ s.date = %q{2011-06-27}
13
13
  s.description = %q{Extensions for the GEOS library.}
14
14
  s.email = %q{code@zoocasa.com}
15
15
  s.extra_rdoc_files = [
@@ -19,36 +19,36 @@ Gem::Specification.new do |s|
19
19
  "MIT-LICENSE",
20
20
  "README.rdoc",
21
21
  "Rakefile",
22
+ "VERSION",
23
+ "app/models/geos/geometry_column.rb",
24
+ "app/models/geos/spatial_ref_sys.rb",
22
25
  "geos-extensions.gemspec",
23
- "lib/active_record_extensions.rb",
24
- "lib/active_record_extensions/connection_adapters/postgresql_adapter.rb",
25
- "lib/active_record_extensions/geometry_columns.rb",
26
- "lib/active_record_extensions/geospatial_scopes.rb",
27
26
  "lib/geos-extensions.rb",
27
+ "lib/geos/active_record_extensions.rb",
28
+ "lib/geos/active_record_extensions/connection_adapters/postgresql_adapter.rb",
29
+ "lib/geos/active_record_extensions/geometry_columns.rb",
30
+ "lib/geos/active_record_extensions/geospatial_scopes.rb",
31
+ "lib/geos/geos_helper.rb",
32
+ "lib/geos/google_maps.rb",
33
+ "lib/geos/google_maps/api_2.rb",
34
+ "lib/geos/google_maps/api_3.rb",
35
+ "lib/geos/google_maps/polyline_encoder.rb",
36
+ "lib/geos/rails/engine.rb",
28
37
  "lib/geos_extensions.rb",
29
- "lib/geos_helper.rb",
30
- "lib/google_maps.rb",
31
- "lib/google_maps/polyline_encoder.rb",
32
- "rails/railtie.rb",
33
- "rails/tasks/test.rake",
38
+ "lib/tasks/test.rake",
34
39
  "test/fixtures/foos.yml",
35
40
  "test/geometry_columns_test.rb",
36
41
  "test/geospatial_scopes_test.rb",
42
+ "test/google_maps_api_2_test.rb",
43
+ "test/google_maps_api_3_test.rb",
37
44
  "test/reader_test.rb",
38
45
  "test/test_helper.rb",
39
46
  "test/writer_test.rb"
40
47
  ]
41
48
  s.homepage = %q{http://github.com/zoocasa/geos-extensions}
42
49
  s.require_paths = ["lib"]
43
- s.rubygems_version = %q{1.6.2}
50
+ s.rubygems_version = %q{1.7.2}
44
51
  s.summary = %q{Extensions for the GEOS library.}
45
- s.test_files = [
46
- "test/geometry_columns_test.rb",
47
- "test/geospatial_scopes_test.rb",
48
- "test/reader_test.rb",
49
- "test/test_helper.rb",
50
- "test/writer_test.rb"
51
- ]
52
52
 
53
53
  if s.respond_to? :specification_version then
54
54
  s.specification_version = 3
@@ -1,8 +1,4 @@
1
1
 
2
- if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
3
- require File.join(GEOS_EXTENSIONS_BASE, *%w{ active_record_extensions connection_adapters postgresql_adapter })
4
- end
5
-
6
2
  module Geos
7
3
  module ActiveRecord #:nodoc:
8
4
 
@@ -185,8 +181,8 @@ module Geos
185
181
 
186
182
  GEOMETRY_COLUMN_OUTPUT_FORMATS.reject { |f| f == 'geos' }.each do |f|
187
183
  src, line = <<-EOF, __LINE__ + 1
188
- def #{k.name}_#{f}
189
- @#{k.name}_#{f} ||= self.#{k.name}_geos.to_#{f} rescue nil
184
+ def #{k.name}_#{f}(*args)
185
+ @#{k.name}_#{f} ||= self.#{k.name}_geos.to_#{f}(*args) rescue nil
190
186
  end
191
187
  EOF
192
188
  self.class_eval(src, __FILE__, line)
@@ -71,8 +71,15 @@ module Geos
71
71
  # * :wkb_options - in order to facilitate some conversions, geometries
72
72
  # are converted to WKB. The default is `{:include_srid => true}` to
73
73
  # force the geometry to use PostGIS's Extended WKB.
74
+ # * :allow_null - relationship scopes have the option of treating NULL
75
+ # geometry values as TRUE, i.e.
76
+ #
77
+ # ST_within(the_geom, ...) OR the_geom IS NULL
78
+ #
74
79
  # * :desc - the order_by scopes have an additional :desc option to alllow
75
80
  # for DESC ordering.
81
+ # * :nulls - the order_by scopes also allow you to specify whether you
82
+ # want NULL values to be sorted first or last.
76
83
  #
77
84
  # == SRID Detection
78
85
  #
@@ -206,7 +213,11 @@ module Geos
206
213
  end
207
214
 
208
215
  ret << ', ?' * function_options[:additional_args]
209
- ret << %{)#{options[:append]}}
216
+ ret << ')'
217
+
218
+ if options[:allow_null]
219
+ ret << " OR #{self.quoted_table_name}.#{column_name} IS NULL"
220
+ end
210
221
  end
211
222
  end
212
223
 
@@ -0,0 +1,10 @@
1
+
2
+ require File.join(Geos::GEOS_EXTENSIONS_BASE, *%w{ geos active_record_extensions connection_adapters postgresql_adapter })
3
+
4
+ module Geos
5
+ module ActiveRecord
6
+ autoload :GeometryColumns, File.join(Geos::GEOS_EXTENSIONS_BASE, %w{ geos active_record_extensions geometry_columns })
7
+ autoload :GeospatialScopes, File.join(Geos::GEOS_EXTENSIONS_BASE, %w{ geos active_record_extensions geospatial_scopes })
8
+ end
9
+ end
10
+
@@ -0,0 +1,104 @@
1
+
2
+ module Geos::Helper
3
+ JS_ESCAPE_MAP = {
4
+ '\\' => '\\\\',
5
+ '</' => '<\/',
6
+ "\r\n" => '\n',
7
+ "\n" => '\n',
8
+ "\r" => '\n',
9
+ '"' => '\\"',
10
+ "'" => "\\'"
11
+ }
12
+
13
+ # Escape carrier returns and single and double quotes for JavaScript
14
+ # segments. Borrowed from ActiveSupport.
15
+ def self.escape_javascript(javascript) #:nodoc:
16
+ if javascript
17
+ javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
18
+ else
19
+ ''
20
+ end
21
+ end
22
+
23
+ def self.escape_json(hash, ignore_keys = [])
24
+ json = hash.inject([]) do |memo, (k, v)|
25
+ memo.tap {
26
+ k = k.to_s
27
+ memo << if ignore_keys.include?(k)
28
+ "#{k.to_json}: #{v}"
29
+ else
30
+ "#{k.to_json}: #{v.to_json}"
31
+ end
32
+ }
33
+ end
34
+
35
+ "{#{json.join(', ')}}"
36
+ end
37
+
38
+ def self.camelize(lower_case_and_underscored_word, first_letter_in_uppercase = false)
39
+ if first_letter_in_uppercase
40
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
41
+ else
42
+ lower_case_and_underscored_word.to_s[0..0].downcase + camelize(lower_case_and_underscored_word, true)[1..-1]
43
+ end
44
+ end
45
+
46
+ def self.xml_options(*args) #:nodoc:
47
+ xml = if Builder::XmlMarkup === args.first
48
+ args.first
49
+ else
50
+ Builder::XmlMarkup.new(:indent => 4)
51
+ end
52
+
53
+ options = if Hash === args.last
54
+ args.last
55
+ else
56
+ Hash.new
57
+ end
58
+
59
+ [ xml, options ]
60
+ end
61
+
62
+ # Return a new Hash with all keys converted to camelized Strings.
63
+ def self.camelize_keys(hash, first_letter_in_uppercase = false)
64
+ hash.inject({}) do |options, (key, value)|
65
+ options[camelize(key, first_letter_in_uppercase)] = value
66
+ options
67
+ end
68
+ end
69
+
70
+ # Destructively convert all keys to camelized Strings.
71
+ def camelize_keys!(hash, first_letter_in_uppercase = false)
72
+ hash.tap {
73
+ hash.keys.each do |key|
74
+ unless key.class.to_s == 'String'
75
+ hash[camelize(key, first_letter_in_uppercase)] = hash[key]
76
+ hash.delete(key)
77
+ end
78
+ end
79
+ }
80
+ end
81
+
82
+ # Deeply camelize a Hash.
83
+ def deep_camelize_keys(hash, first_letter_in_uppercase = false)
84
+ camelize_keys(hash, first_letter_in_upppcase).inject({}) do |memo, (k, v)|
85
+ memo.tap do
86
+ if v.is_a? Hash
87
+ memo[k] = deep_camelize_keys(v, first_letter_in_uppercase)
88
+ else
89
+ memo[k] = v
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # Destructively deeply camelize a Hash.
96
+ def deep_camelize_keys!(hash, first_letter_in_uppercase = false)
97
+ hash.replace(deep_camelize_keys(hash, first_letter_in_uppercase))
98
+ end
99
+
100
+ def self.number_with_precision(number, precision = 6)
101
+ rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
102
+ "%01.#{precision}f" % rounded_number
103
+ end
104
+ end
@@ -0,0 +1,180 @@
1
+
2
+ module Geos::GoogleMaps::Api2
3
+ module Geometry
4
+ # Returns a new GLatLngBounds object with the proper GLatLngs in place
5
+ # for determining the geometry bounds.
6
+ def to_g_lat_lng_bounds_api2(options = {})
7
+ klass = if options[:short_class]
8
+ 'GLatLngBounds'
9
+ else
10
+ 'google.maps.LatLngBounds'
11
+ end
12
+
13
+ "new #{klass}(#{self.lower_left.to_g_lat_lng_api2(options)}, #{self.upper_right.to_g_lat_lng_api2(options)})"
14
+ end
15
+
16
+ # Returns a String in Google Maps' GLatLngBounds#toString() format.
17
+ def to_g_lat_lng_bounds_string_api2(precision = 10)
18
+ "((#{self.lower_left.to_g_url_value_api2(precision)}), (#{self.upper_right.to_g_url_value_api2(precision)}))"
19
+ end
20
+
21
+ # Returns a new GPolyline.
22
+ def to_g_polyline_api2(polyline_options = {}, options = {})
23
+ self.coord_seq.to_g_polyline_api2(polyline_options, options)
24
+ end
25
+
26
+ # Returns a new GPolygon.
27
+ def to_g_polygon_api2(polygon_options = {}, options = {})
28
+ self.coord_seq.to_g_polygon_api2(polygon_options, options)
29
+ end
30
+
31
+ # Returns a new GMarker at the centroid of the geometry. The options
32
+ # Hash works the same as the Google Maps API GMarkerOptions class does,
33
+ # but allows for underscored Ruby-like options which are then converted
34
+ # to the appropriate camel-cased Javascript options.
35
+ def to_g_marker_api2(marker_options = {}, options = {})
36
+ klass = if options[:short_class]
37
+ 'GMarker'
38
+ else
39
+ 'google.maps.Marker'
40
+ end
41
+
42
+ opts = Geos::Helper.camelize_keys(marker_options)
43
+
44
+ "new #{klass}(#{self.centroid.to_g_lat_lng(options)}, #{opts.to_json})"
45
+ end
46
+ end
47
+
48
+ module CoordinateSequence
49
+ # Returns a Ruby Array of GLatLngs.
50
+ def to_g_lat_lng_api2(options = {})
51
+ klass = if options[:short_class]
52
+ 'GLatLng'
53
+ else
54
+ 'google.maps.LatLng'
55
+ end
56
+
57
+ self.to_a.collect do |p|
58
+ "new #{klass}(#{p[1]}, #{p[0]})"
59
+ end
60
+ end
61
+
62
+ # Returns a new GPolyline. Note that this GPolyline just uses whatever
63
+ # coordinates are found in the sequence in order, so it might not
64
+ # make much sense at all.
65
+ #
66
+ # The options Hash follows the Google Maps API arguments to the
67
+ # GPolyline constructor and include :color, :weight, :opacity and
68
+ # :options. 'null' is used in place of any unset options.
69
+ def to_g_polyline_api2(polyline_options = {}, options = {})
70
+ klass = if options[:short_class]
71
+ 'GPolyline'
72
+ else
73
+ 'google.maps.Polyline'
74
+ end
75
+
76
+ poly_opts = if polyline_options[:polyline_options]
77
+ Geos::Helper.camelize_keys(polyline_options[:polyline_options])
78
+ end
79
+
80
+ args = [
81
+ (polyline_options[:color] ? "'#{Geos::Helper.escape_javascript(polyline_options[:color])}'" : 'null'),
82
+ (polyline_options[:weight] || 'null'),
83
+ (polyline_options[:opacity] || 'null'),
84
+ (poly_opts ? poly_opts.to_json : 'null')
85
+ ].join(', ')
86
+
87
+ "new #{klass}([#{self.to_g_lat_lng(options).join(', ')}], #{args})"
88
+ end
89
+
90
+ # Returns a new GPolygon. Note that this GPolygon just uses whatever
91
+ # coordinates are found in the sequence in order, so it might not
92
+ # make much sense at all.
93
+ #
94
+ # The options Hash follows the Google Maps API arguments to the
95
+ # GPolygon constructor and include :stroke_color, :stroke_weight,
96
+ # :stroke_opacity, :fill_color, :fill_opacity and :options. 'null' is
97
+ # used in place of any unset options.
98
+ def to_g_polygon_api2(polygon_options = {}, options = {})
99
+ klass = if options[:short_class]
100
+ 'GPolygon'
101
+ else
102
+ 'google.maps.Polygon'
103
+ end
104
+
105
+ poly_opts = if polygon_options[:polygon_options]
106
+ Geos::Helper.camelize_keys(polygon_options[:polygon_options])
107
+ end
108
+
109
+ args = [
110
+ (polygon_options[:stroke_color] ? "'#{Geos::Helper.escape_javascript(polygon_options[:stroke_color])}'" : 'null'),
111
+ (polygon_options[:stroke_weight] || 'null'),
112
+ (polygon_options[:stroke_opacity] || 'null'),
113
+ (polygon_options[:fill_color] ? "'#{Geos::Helper.escape_javascript(polygon_options[:fill_color])}'" : 'null'),
114
+ (polygon_options[:fill_opacity] || 'null'),
115
+ (poly_opts ? poly_opts.to_json : 'null')
116
+ ].join(', ')
117
+ "new #{klass}([#{self.to_g_lat_lng_api2(options).join(', ')}], #{args})"
118
+ end
119
+ end
120
+
121
+ module Point
122
+ # Returns a new GLatLng.
123
+ def to_g_lat_lng_api2(options = {})
124
+ klass = if options[:short_class]
125
+ 'GLatLng'
126
+ else
127
+ 'google.maps.LatLng'
128
+ end
129
+
130
+ "new #{klass}(#{self.lat}, #{self.lng})"
131
+ end
132
+
133
+ # Returns a new GPoint
134
+ def to_g_point_api2(options = {})
135
+ klass = if options[:short_class]
136
+ 'GPoint'
137
+ else
138
+ 'google.maps.Point'
139
+ end
140
+
141
+ "new #{klass}(#{self.x}, #{self.y})"
142
+ end
143
+ end
144
+
145
+ module Polygon
146
+ # Returns a GPolyline of the exterior ring of the Polygon. This does
147
+ # not take into consideration any interior rings the Polygon may
148
+ # have.
149
+ def to_g_polyline_api2(polyline_options = {}, options = {})
150
+ self.exterior_ring.to_g_polyline_api2(polyline_options, options)
151
+ end
152
+
153
+ # Returns a GPolygon of the exterior ring of the Polygon. This does
154
+ # not take into consideration any interior rings the Polygon may
155
+ # have.
156
+ def to_g_polygon_api2(polygon_options = {}, options = {})
157
+ self.exterior_ring.to_g_polygon_api2(polygon_options, options)
158
+ end
159
+ end
160
+
161
+ module GeometryCollection
162
+ # Returns a Ruby Array of GPolylines for each geometry in the
163
+ # collection.
164
+ def to_g_polyline_api2(polyline_options = {}, options = {})
165
+ self.collect do |p|
166
+ p.to_g_polyline_api2(polyline_options, options)
167
+ end
168
+ end
169
+
170
+ # Returns a Ruby Array of GPolygons for each geometry in the
171
+ # collection.
172
+ def to_g_polygon_api2(polygon_options = {}, options = {})
173
+ self.collect do |p|
174
+ p.to_g_polygon_api2(polygon_options, options)
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ Geos::GoogleMaps.use_api(2)