geos-extensions 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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)