geos-extensions 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +3 -0
- data/Gemfile +17 -0
- data/Guardfile +17 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +19 -91
- data/Rakefile +1 -12
- data/geos-extensions.gemspec +1 -9
- data/lib/geos-extensions.rb +1 -9
- data/lib/geos/coordinate_sequence.rb +92 -0
- data/lib/geos/extensions/version.rb +1 -1
- data/lib/geos/geometry.rb +252 -0
- data/lib/geos/geometry_collection.rb +60 -0
- data/lib/geos/geos_helper.rb +86 -72
- data/lib/geos/google_maps.rb +1 -0
- data/lib/geos/google_maps/api_2.rb +9 -23
- data/lib/geos/google_maps/api_3.rb +10 -24
- data/lib/geos/google_maps/api_common.rb +41 -0
- data/lib/geos/line_string.rb +15 -0
- data/lib/geos/multi_line_string.rb +15 -0
- data/lib/geos/multi_point.rb +15 -0
- data/lib/geos/multi_polygon.rb +27 -0
- data/lib/geos/point.rb +120 -0
- data/lib/geos/polygon.rb +158 -0
- data/lib/geos/yaml.rb +30 -0
- data/lib/geos/yaml/psych.rb +18 -0
- data/lib/geos/yaml/syck.rb +41 -0
- data/lib/geos_extensions.rb +110 -711
- data/test/google_maps_api_2_tests.rb +54 -32
- data/test/google_maps_api_3_tests.rb +58 -36
- data/test/google_maps_polyline_encoder_tests.rb +1 -1
- data/test/helper_tests.rb +28 -0
- data/test/misc_tests.rb +130 -10
- data/test/reader_tests.rb +38 -1
- data/test/test_helper.rb +54 -146
- data/test/writer_tests.rb +329 -10
- data/test/yaml_tests.rb +203 -0
- metadata +26 -102
- data/app/models/geos/geometry_column.rb +0 -39
- data/app/models/geos/spatial_ref_sys.rb +0 -12
- data/lib/geos/active_record_extensions.rb +0 -12
- data/lib/geos/active_record_extensions/connection_adapters/postgresql_adapter.rb +0 -151
- data/lib/geos/active_record_extensions/spatial_columns.rb +0 -367
- data/lib/geos/active_record_extensions/spatial_scopes.rb +0 -493
- data/lib/geos/rails/engine.rb +0 -6
- data/lib/tasks/test.rake +0 -42
- data/test/adapter_tests.rb +0 -38
- data/test/database.yml +0 -17
- data/test/fixtures/foo3ds.yml +0 -16
- data/test/fixtures/foo_geographies.yml +0 -16
- data/test/fixtures/foos.yml +0 -16
- data/test/geography_columns_tests.rb +0 -176
- data/test/geometry_columns_tests.rb +0 -178
- data/test/spatial_scopes_geographies_tests.rb +0 -107
- data/test/spatial_scopes_tests.rb +0 -337
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class MultiPolygon < GeometryCollection
|
4
|
+
def to_geojsonable(options = {})
|
5
|
+
options = {
|
6
|
+
:interior_rings => true
|
7
|
+
}.merge(options)
|
8
|
+
|
9
|
+
{
|
10
|
+
:type => 'MultiPolygon',
|
11
|
+
:coordinates => self.to_a.collect { |polygon|
|
12
|
+
coords = [ polygon.exterior_ring.coord_seq.to_a ]
|
13
|
+
|
14
|
+
if options[:interior_rings] && polygon.num_interior_rings > 0
|
15
|
+
coords.concat polygon.interior_rings.collect { |r|
|
16
|
+
r.coord_seq.to_a
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
coords
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
alias :as_geojson :to_geojsonable
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
data/lib/geos/point.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class Point
|
4
|
+
unless method_defined?(:y)
|
5
|
+
# Returns the Y coordinate of the Point.
|
6
|
+
def y
|
7
|
+
self.to_a[1]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
%w{
|
12
|
+
latitude lat north south n s
|
13
|
+
}.each do |name|
|
14
|
+
self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
|
15
|
+
alias #{name} :y
|
16
|
+
EOF
|
17
|
+
end
|
18
|
+
|
19
|
+
unless method_defined?(:x)
|
20
|
+
# Returns the X coordinate of the Point.
|
21
|
+
def x
|
22
|
+
self.to_a[0]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
%w{
|
27
|
+
longitude lng east west e w
|
28
|
+
}.each do |name|
|
29
|
+
self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
|
30
|
+
alias #{name} :x
|
31
|
+
EOF
|
32
|
+
end
|
33
|
+
|
34
|
+
unless method_defined?(:z)
|
35
|
+
# Returns the Z coordinate of the Point.
|
36
|
+
def z
|
37
|
+
if self.has_z?
|
38
|
+
self.to_a[2]
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the Point's coordinates as an Array in the following format:
|
46
|
+
#
|
47
|
+
# [ x, y, z ]
|
48
|
+
#
|
49
|
+
# The Z coordinate will only be present for Points which have a Z
|
50
|
+
# dimension.
|
51
|
+
def to_a
|
52
|
+
if defined?(@to_a)
|
53
|
+
@to_a
|
54
|
+
else
|
55
|
+
cs = self.coord_seq
|
56
|
+
@to_a = if self.has_z?
|
57
|
+
[ cs.get_x(0), cs.get_y(0), cs.get_z(0) ]
|
58
|
+
else
|
59
|
+
[ cs.get_x(0), cs.get_y(0) ]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Optimize some unnecessary code away:
|
65
|
+
%w{
|
66
|
+
upper_left upper_right lower_right lower_left
|
67
|
+
ne nw se sw
|
68
|
+
northwest northeast southeast southwest
|
69
|
+
}.each do |name|
|
70
|
+
self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
|
71
|
+
def #{name}
|
72
|
+
self
|
73
|
+
end
|
74
|
+
EOF
|
75
|
+
end
|
76
|
+
|
77
|
+
# Build some XmlMarkup for KML. You can set KML options for extrude and
|
78
|
+
# altitudeMode. Use Rails/Ruby-style code and it will be converted
|
79
|
+
# appropriately, i.e. :altitude_mode, not :altitudeMode.
|
80
|
+
def to_kml(*args)
|
81
|
+
xml, options = Geos::Helper.xml_options(*args)
|
82
|
+
xml.Point(:id => options[:id]) do
|
83
|
+
xml.extrude(options[:extrude]) if options[:extrude]
|
84
|
+
xml.altitudeMode(Geos::Helper.camelize(options[:altitude_mode])) if options[:altitude_mode]
|
85
|
+
xml.coordinates(self.to_a.join(','))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Build some XmlMarkup for GeoRSS. You should include the
|
90
|
+
# appropriate georss and gml XML namespaces in your document.
|
91
|
+
def to_georss(*args)
|
92
|
+
xml = Geos::Helper.xml_options(*args)[0]
|
93
|
+
xml.georss(:where) do
|
94
|
+
xml.gml(:Point) do
|
95
|
+
xml.gml(:pos, "#{self.lat} #{self.lng}")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns a Hash suitable for converting to JSON.
|
101
|
+
def as_json(options = {})
|
102
|
+
cs = self.coord_seq
|
103
|
+
if self.has_z?
|
104
|
+
{ :type => 'point', :lat => cs.get_y(0), :lng => cs.get_x(0), :z => cs.get_z(0) }
|
105
|
+
else
|
106
|
+
{ :type => 'point', :lat => cs.get_y(0), :lng => cs.get_x(0) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
alias :to_jsonable :as_json
|
110
|
+
|
111
|
+
def as_geojson(options = {})
|
112
|
+
{
|
113
|
+
:type => 'Point',
|
114
|
+
:coordinates => self.to_a
|
115
|
+
}
|
116
|
+
end
|
117
|
+
alias :to_geojsonable :as_geojson
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
data/lib/geos/polygon.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class Polygon
|
4
|
+
# Build some XmlMarkup for XML. You can set various KML options like
|
5
|
+
# tessellate, altitudeMode, etc. Use Rails/Ruby-style code and it
|
6
|
+
# will be converted automatically, i.e. :altitudeMode, not
|
7
|
+
# :altitude_mode. You can also include interior rings by setting
|
8
|
+
# :interior_rings to true. The default is false.
|
9
|
+
def to_kml(*args)
|
10
|
+
xml, options = Geos::Helper.xml_options(*args)
|
11
|
+
|
12
|
+
xml.Polygon(:id => options[:id]) do
|
13
|
+
xml.extrude(options[:extrude]) if options[:extrude]
|
14
|
+
xml.tessellate(options[:tessellate]) if options[:tessellate]
|
15
|
+
xml.altitudeMode(Geos::Helper.camelize(options[:altitude_mode])) if options[:altitude_mode]
|
16
|
+
xml.outerBoundaryIs do
|
17
|
+
xml.LinearRing do
|
18
|
+
xml.coordinates do
|
19
|
+
xml << self.exterior_ring.coord_seq.to_a.collect do |p|
|
20
|
+
p.join(',')
|
21
|
+
end.join(' ')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
(0...self.num_interior_rings).to_a.each do |n|
|
26
|
+
xml.innerBoundaryIs do
|
27
|
+
xml.LinearRing do
|
28
|
+
xml.coordinates do
|
29
|
+
xml << self.interior_ring_n(n).coord_seq.to_a.collect do |p|
|
30
|
+
p.join(',')
|
31
|
+
end.join(' ')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end if options[:interior_rings] && self.num_interior_rings > 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Build some XmlMarkup for GeoRSS. You should include the
|
40
|
+
# appropriate georss and gml XML namespaces in your document.
|
41
|
+
def to_georss(*args)
|
42
|
+
xml = Geos::Helper.xml_options(*args)[0]
|
43
|
+
|
44
|
+
xml.georss(:where) do
|
45
|
+
xml.gml(:Polygon) do
|
46
|
+
xml.gml(:exterior) do
|
47
|
+
xml.gml(:LinearRing) do
|
48
|
+
xml.gml(:posList) do
|
49
|
+
xml << self.exterior_ring.coord_seq.to_a.collect do |p|
|
50
|
+
"#{p[1]} #{p[0]}"
|
51
|
+
end.join(' ')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns a Hash suitable for converting to JSON.
|
60
|
+
#
|
61
|
+
# Options:
|
62
|
+
#
|
63
|
+
# * :encoded - enable or disable Google Maps encoding. The default is
|
64
|
+
# true.
|
65
|
+
# * :level - set the level of the Google Maps encoding algorithm.
|
66
|
+
# * :interior_rings - add interior rings to the output. The default
|
67
|
+
# is false.
|
68
|
+
# * :style_options - any style options you want to pass along in the
|
69
|
+
# JSON. These options will be automatically camelized into
|
70
|
+
# Javascripty code.
|
71
|
+
def as_json(options = {})
|
72
|
+
options = {
|
73
|
+
:encoded => true,
|
74
|
+
:level => 3,
|
75
|
+
:interior_rings => false
|
76
|
+
}.merge options
|
77
|
+
|
78
|
+
style_options = Hash.new
|
79
|
+
if options[:style_options] && !options[:style_options].empty?
|
80
|
+
options[:style_options].each do |k, v|
|
81
|
+
style_options[Geos::Helper.camelize(k.to_s)] = v
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
if options[:encoded]
|
86
|
+
ret = {
|
87
|
+
:type => 'polygon',
|
88
|
+
:encoded => true,
|
89
|
+
:polylines => [ Geos::GoogleMaps::PolylineEncoder.encode(
|
90
|
+
self.exterior_ring.coord_seq.to_a,
|
91
|
+
options[:level]
|
92
|
+
).merge(:bounds => {
|
93
|
+
:sw => self.lower_left.to_a,
|
94
|
+
:ne => self.upper_right.to_a
|
95
|
+
})
|
96
|
+
],
|
97
|
+
:options => style_options
|
98
|
+
}
|
99
|
+
|
100
|
+
if options[:interior_rings] && self.num_interior_rings > 0
|
101
|
+
(0..(self.num_interior_rings) - 1).to_a.each do |n|
|
102
|
+
ret[:polylines] << Geos::GoogleMaps::PolylineEncoder.encode(
|
103
|
+
self.interior_ring_n(n).coord_seq.to_a,
|
104
|
+
options[:level]
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
ret
|
109
|
+
else
|
110
|
+
ret = {
|
111
|
+
:type => 'polygon',
|
112
|
+
:encoded => false,
|
113
|
+
:polylines => [{
|
114
|
+
:points => self.exterior_ring.coord_seq.to_a,
|
115
|
+
:bounds => {
|
116
|
+
:sw => self.lower_left.to_a,
|
117
|
+
:ne => self.upper_right.to_a
|
118
|
+
}
|
119
|
+
}]
|
120
|
+
}
|
121
|
+
if options[:interior_rings] && self.num_interior_rings > 0
|
122
|
+
(0..(self.num_interior_rings) - 1).to_a.each do |n|
|
123
|
+
ret[:polylines] << {
|
124
|
+
:points => self.interior_ring_n(n).coord_seq.to_a
|
125
|
+
}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
ret
|
129
|
+
end
|
130
|
+
end
|
131
|
+
alias :to_jsonable :as_json
|
132
|
+
|
133
|
+
# Options:
|
134
|
+
#
|
135
|
+
# * :interior_rings - whether to include any interior rings in the output.
|
136
|
+
# The default is true.
|
137
|
+
def as_geojson(options = {})
|
138
|
+
options = {
|
139
|
+
:interior_rings => true
|
140
|
+
}.merge(options)
|
141
|
+
|
142
|
+
ret = {
|
143
|
+
:type => 'Polygon',
|
144
|
+
:coordinates => [ self.exterior_ring.coord_seq.to_a ]
|
145
|
+
}
|
146
|
+
|
147
|
+
if options[:interior_rings] && self.num_interior_rings > 0
|
148
|
+
ret[:coordinates].concat self.interior_rings.collect { |r|
|
149
|
+
r.coord_seq.to_a
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
ret
|
154
|
+
end
|
155
|
+
alias :to_geojsonable :as_geojson
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
data/lib/geos/yaml.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# This file adds yaml serialization support to geometries. The generated yaml
|
4
|
+
# has this format:
|
5
|
+
#
|
6
|
+
# !ruby/object:Geos::Geometry
|
7
|
+
# geom: SRID=4326; POINT (-104.97 39.71)
|
8
|
+
#
|
9
|
+
# So to use this in a rails fixture file you could do something like this:
|
10
|
+
#
|
11
|
+
# geometry_1:
|
12
|
+
# id: 1
|
13
|
+
# geom: !ruby/object:Geos::Geometry
|
14
|
+
# geom: SRID=4326; POINT (-104.97 39.71)
|
15
|
+
#
|
16
|
+
# Note this code assumes the use of Psych (not syck) and ruby 1.9 and higher
|
17
|
+
|
18
|
+
require 'yaml'
|
19
|
+
|
20
|
+
dirname = File.join(File.dirname(__FILE__), 'yaml')
|
21
|
+
|
22
|
+
# Ruby 2.0 check
|
23
|
+
if Object.const_defined?(:Psych) && YAML == Psych
|
24
|
+
require File.join(dirname, 'psych')
|
25
|
+
# Ruby 1.9 check
|
26
|
+
elsif YAML.const_defined?('ENGINE') && YAML::ENGINE.yamler = 'psych'
|
27
|
+
require File.join(dirname, 'psych')
|
28
|
+
else
|
29
|
+
require File.join(dirname, 'syck')
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Geos
|
4
|
+
class Geometry
|
5
|
+
def init_with(coder)
|
6
|
+
# Convert wkt to a geos pointer
|
7
|
+
@ptr = Geos.read(coder['geom']).ptr
|
8
|
+
end
|
9
|
+
|
10
|
+
def encode_with(coder)
|
11
|
+
# Note we enforce ASCII encoding so the geom in the YAML file is
|
12
|
+
# readable -- otherwise psych converts it to a binary string.
|
13
|
+
coder['geom'] = self.to_ewkt(
|
14
|
+
:include_srid => self.srid != 0
|
15
|
+
).force_encoding('ASCII')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Geos
|
4
|
+
class Geometry
|
5
|
+
yaml_as "tag:ruby.yaml.org,2002:object:Geos::Geometry"
|
6
|
+
|
7
|
+
def taguri
|
8
|
+
"tag:ruby.yaml.org,2002:object:#{self.class.name}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.yaml_new(klass, tag, val)
|
12
|
+
Geos.read(val['geom'])
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_yaml( opts = {} )
|
16
|
+
YAML::quick_emit(self.object_id, opts) do |out|
|
17
|
+
out.map(taguri) do |map|
|
18
|
+
map.add('geom', self.to_ewkt(
|
19
|
+
:include_srid => self.srid != 0
|
20
|
+
))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Point
|
27
|
+
yaml_as "tag:ruby.yaml.org,2002:object:Geos::Point"
|
28
|
+
end
|
29
|
+
|
30
|
+
class LineString
|
31
|
+
yaml_as "tag:ruby.yaml.org,2002:object:Geos::LineString"
|
32
|
+
end
|
33
|
+
|
34
|
+
class Polygon
|
35
|
+
yaml_as "tag:ruby.yaml.org,2002:object:Geos::Polygon"
|
36
|
+
end
|
37
|
+
|
38
|
+
class GeometryCollection
|
39
|
+
yaml_as "tag:ruby.yaml.org,2002:object:Geos::GeometryCollection"
|
40
|
+
end
|
41
|
+
end
|
data/lib/geos_extensions.rb
CHANGED
@@ -9,14 +9,24 @@ end
|
|
9
9
|
require 'geos' unless defined?(Geos)
|
10
10
|
|
11
11
|
require File.join(File.dirname(__FILE__), *%w{ geos extensions version })
|
12
|
+
require File.join(File.dirname(__FILE__), *%w{ geos yaml })
|
12
13
|
|
13
14
|
# Some custom extensions to the SWIG-based Geos Ruby extension.
|
14
15
|
module Geos
|
15
16
|
GEOS_EXTENSIONS_BASE = File.join(File.dirname(__FILE__))
|
16
17
|
GEOS_EXTENSIONS_VERSION = Geos::Extensions::VERSION
|
17
18
|
|
19
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos geometry })
|
20
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos coordinate_sequence })
|
21
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos point })
|
22
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos line_string })
|
23
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos polygon })
|
24
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos geometry_collection })
|
25
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos multi_polygon })
|
26
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos multi_line_string })
|
27
|
+
require File.join(GEOS_EXTENSIONS_BASE, *%w{ geos multi_point })
|
28
|
+
|
18
29
|
autoload :Helper, File.join(GEOS_EXTENSIONS_BASE, *%w{ geos geos_helper })
|
19
|
-
autoload :ActiveRecord, File.join(GEOS_EXTENSIONS_BASE, *%w{ geos active_record_extensions })
|
20
30
|
autoload :GoogleMaps, File.join(GEOS_EXTENSIONS_BASE, *%w{ geos google_maps })
|
21
31
|
|
22
32
|
REGEXP_FLOAT = /(-?\d*(?:\.\d+)?|-?\d*(?:\.\d+?)[eE][-+]?\d+)/
|
@@ -76,27 +86,67 @@ module Geos
|
|
76
86
|
geom
|
77
87
|
end
|
78
88
|
|
89
|
+
ALLOWED_GEOS_READ_TYPES = [
|
90
|
+
:geometry,
|
91
|
+
:wkt,
|
92
|
+
:wkb,
|
93
|
+
:wkb_hex,
|
94
|
+
:g_lat_lng_bounds,
|
95
|
+
:g_lat_lng,
|
96
|
+
:box2d,
|
97
|
+
:wkb,
|
98
|
+
:nil
|
99
|
+
]
|
100
|
+
|
79
101
|
# Tries its best to return a Geometry object.
|
80
102
|
def self.read(geom, options = {})
|
81
|
-
|
103
|
+
allowed = Geos::Helper.array_wrap(options[:allowed] || ALLOWED_GEOS_READ_TYPES)
|
104
|
+
allowed = allowed - Geos::Helper.array_wrap(options[:excluded])
|
105
|
+
|
106
|
+
geom = geom.dup.force_encoding('BINARY') if geom.respond_to?(:force_encoding)
|
107
|
+
|
108
|
+
type = case geom
|
82
109
|
when Geos::Geometry
|
83
|
-
|
110
|
+
:geometry
|
84
111
|
when REGEXP_WKT
|
85
|
-
|
112
|
+
:wkt
|
86
113
|
when REGEXP_WKB_HEX
|
87
|
-
|
88
|
-
when REGEXP_G_LAT_LNG_BOUNDS
|
89
|
-
|
114
|
+
:wkb_hex
|
115
|
+
when REGEXP_G_LAT_LNG_BOUNDS
|
116
|
+
:g_lat_lng_bounds
|
117
|
+
when REGEXP_G_LAT_LNG
|
118
|
+
:g_lat_lng
|
90
119
|
when REGEXP_BOX2D
|
91
|
-
|
120
|
+
:box2d
|
92
121
|
when String
|
93
|
-
|
122
|
+
:wkb
|
94
123
|
when nil
|
95
|
-
nil
|
124
|
+
:nil
|
96
125
|
else
|
97
126
|
raise ArgumentError.new("Invalid geometry!")
|
98
127
|
end
|
99
128
|
|
129
|
+
if !allowed.include?(type)
|
130
|
+
raise ArgumentError.new("geom appears to be a #{type} but #{type} is being filtered")
|
131
|
+
end
|
132
|
+
|
133
|
+
geos = case type
|
134
|
+
when :geometry
|
135
|
+
geom
|
136
|
+
when :wkt
|
137
|
+
Geos.from_wkt($~, options)
|
138
|
+
when :wkb_hex
|
139
|
+
Geos.from_wkb(geom, options)
|
140
|
+
when :g_lat_lng_bounds, :g_lat_lng
|
141
|
+
Geos.from_g_lat_lng($~, options)
|
142
|
+
when :box2d
|
143
|
+
Geos.from_box2d($~)
|
144
|
+
when :wkb
|
145
|
+
Geos.from_wkb(geom.unpack('H*').first.upcase, options)
|
146
|
+
when :nil
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
|
100
150
|
if geos && options[:srid]
|
101
151
|
geos.srid = options[:srid]
|
102
152
|
end
|
@@ -106,8 +156,13 @@ module Geos
|
|
106
156
|
|
107
157
|
# Returns some kind of Geometry object from the given WKT. This method
|
108
158
|
# will also accept PostGIS-style EWKT and its various enhancements.
|
109
|
-
def self.from_wkt(
|
110
|
-
srid, raw_wkt =
|
159
|
+
def self.from_wkt(wkt_or_match_data, options = {})
|
160
|
+
srid, raw_wkt = if wkt_or_match_data.kind_of?(MatchData)
|
161
|
+
[ wkt_or_match_data[1], wkt_or_match_data[2] ]
|
162
|
+
else
|
163
|
+
wkt_or_match_data.scan(REGEXP_WKT).first
|
164
|
+
end
|
165
|
+
|
111
166
|
geom = self.wkt_reader_singleton.read(raw_wkt.upcase)
|
112
167
|
geom.srid = (options[:srid] || srid).to_i if options[:srid] || srid
|
113
168
|
geom
|
@@ -121,729 +176,73 @@ module Geos
|
|
121
176
|
# while for GLatLngBounds we return a Geos::Polygon that encompasses the
|
122
177
|
# bounds. Use the option :points to interpret the incoming value as
|
123
178
|
# as GPoints rather than GLatLngs.
|
124
|
-
def self.from_g_lat_lng(
|
125
|
-
|
126
|
-
when
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
}
|
131
|
-
|
132
|
-
unless options[:points]
|
133
|
-
coords.each do |c|
|
134
|
-
c.reverse!
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
Geos.from_wkt("LINESTRING(%s, %s)" % [
|
139
|
-
coords[0].join(' '),
|
140
|
-
coords[1].join(' ')
|
141
|
-
]).envelope
|
142
|
-
when REGEXP_G_LAT_LNG
|
143
|
-
coords = $~.captures.collect(&:to_f).tap { |c|
|
144
|
-
c.reverse! unless options[:points]
|
145
|
-
}
|
146
|
-
Geos.from_wkt("POINT(#{coords.join(' ')})")
|
179
|
+
def self.from_g_lat_lng(geometry_or_match_data, options = {})
|
180
|
+
match_data = case geometry_or_match_data
|
181
|
+
when MatchData
|
182
|
+
geometry_or_match_data.captures
|
183
|
+
when REGEXP_G_LAT_LNG_BOUNDS, REGEXP_G_LAT_LNG
|
184
|
+
$~.captures
|
147
185
|
else
|
148
186
|
raise "Invalid GLatLng format"
|
149
187
|
end
|
150
188
|
|
151
|
-
if
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
geom
|
156
|
-
end
|
157
|
-
|
158
|
-
# Same as from_g_lat_lng but uses GPoints instead of GLatLngs and GBounds
|
159
|
-
# instead of GLatLngBounds. Equivalent to calling from_g_lat_lng with a
|
160
|
-
# non-false expression for the points parameter.
|
161
|
-
def self.from_g_point(geometry, options = {})
|
162
|
-
self.from_g_lat_lng(geometry, options.merge(:points => true))
|
163
|
-
end
|
164
|
-
|
165
|
-
# Creates a Geometry from a PostGIS-style BOX string.
|
166
|
-
def self.from_box2d(geometry)
|
167
|
-
if geometry =~ REGEXP_BOX2D
|
168
|
-
coords = []
|
169
|
-
$~.captures.compact.each_slice(2) { |f|
|
189
|
+
geom = if match_data.length > 3
|
190
|
+
coords = Array.new
|
191
|
+
match_data.compact.each_slice(2) { |f|
|
170
192
|
coords << f.collect(&:to_f)
|
171
193
|
}
|
172
194
|
|
195
|
+
unless options[:points]
|
196
|
+
coords.each do |c|
|
197
|
+
c.reverse!
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
173
201
|
Geos.from_wkt("LINESTRING(%s, %s)" % [
|
174
202
|
coords[0].join(' '),
|
175
203
|
coords[1].join(' ')
|
176
204
|
]).envelope
|
177
205
|
else
|
178
|
-
|
179
|
-
|
180
|
-
end
|
181
|
-
|
182
|
-
# This is our base module that we use for some generic methods used all
|
183
|
-
# over the place.
|
184
|
-
class Geometry
|
185
|
-
protected
|
186
|
-
|
187
|
-
WKB_WRITER_OPTIONS = [ :output_dimensions, :byte_order, :include_srid ].freeze
|
188
|
-
def wkb_writer(options = {}) #:nodoc:
|
189
|
-
writer = WkbWriter.new
|
190
|
-
options.reject { |k, v| !WKB_WRITER_OPTIONS.include?(k) }.each do |k, v|
|
191
|
-
writer.send("#{k}=", v)
|
192
|
-
end
|
193
|
-
writer
|
194
|
-
end
|
195
|
-
|
196
|
-
public
|
197
|
-
|
198
|
-
# Spits the geometry out into WKB in binary.
|
199
|
-
#
|
200
|
-
# You can set the :output_dimensions, :byte_order and :include_srid
|
201
|
-
# options via the options Hash.
|
202
|
-
def to_wkb_bin(options = {})
|
203
|
-
wkb_writer(options).write(self)
|
204
|
-
end
|
205
|
-
|
206
|
-
# Quickly call to_wkb_bin with :include_srid set to true.
|
207
|
-
def to_ewkb_bin(options = {})
|
208
|
-
options = {
|
209
|
-
:include_srid => true
|
210
|
-
}.merge options
|
211
|
-
to_wkb_bin(options)
|
212
|
-
end
|
213
|
-
|
214
|
-
# Spits the geometry out into WKB in hex.
|
215
|
-
#
|
216
|
-
# You can set the :output_dimensions, :byte_order and :include_srid
|
217
|
-
# options via the options Hash.
|
218
|
-
def to_wkb(options = {})
|
219
|
-
wkb_writer(options).write_hex(self)
|
220
|
-
end
|
221
|
-
|
222
|
-
# Quickly call to_wkb with :include_srid set to true.
|
223
|
-
def to_ewkb(options = {})
|
224
|
-
options = {
|
225
|
-
:include_srid => true
|
226
|
-
}.merge options
|
227
|
-
to_wkb(options)
|
228
|
-
end
|
229
|
-
|
230
|
-
# Spits the geometry out into WKT. You can specify the :include_srid
|
231
|
-
# option to create a PostGIS-style EWKT output.
|
232
|
-
def to_wkt(options = {})
|
233
|
-
writer = WktWriter.new
|
234
|
-
|
235
|
-
# Older versions of the Geos library don't allow for options here.
|
236
|
-
args = if WktWriter.instance_method(:write).arity < -1
|
237
|
-
[ options ]
|
238
|
-
else
|
239
|
-
[]
|
240
|
-
end
|
241
|
-
|
242
|
-
ret = ''
|
243
|
-
|
244
|
-
if options[:include_srid]
|
245
|
-
srid = if options[:srid]
|
246
|
-
options[:srid]
|
247
|
-
else
|
248
|
-
self.srid
|
249
|
-
end
|
250
|
-
|
251
|
-
ret << "SRID=#{srid};"
|
252
|
-
end
|
253
|
-
|
254
|
-
ret << writer.write(self, *args)
|
255
|
-
ret
|
256
|
-
end
|
257
|
-
|
258
|
-
# Quickly call to_wkt with :include_srid set to true.
|
259
|
-
def to_ewkt(options = {})
|
260
|
-
options = {
|
261
|
-
:include_srid => true
|
262
|
-
}.merge options
|
263
|
-
to_wkt(options)
|
264
|
-
end
|
265
|
-
|
266
|
-
# Returns a Point for the envelope's upper left coordinate.
|
267
|
-
def upper_left
|
268
|
-
if defined?(@upper_left)
|
269
|
-
@upper_left
|
270
|
-
else
|
271
|
-
cs = self.envelope.exterior_ring.coord_seq
|
272
|
-
@upper_left = Geos::wkt_reader_singleton.read("POINT(#{cs.get_x(3)} #{cs.get_y(3)})")
|
273
|
-
end
|
274
|
-
end
|
275
|
-
alias :nw :upper_left
|
276
|
-
alias :northwest :upper_left
|
277
|
-
|
278
|
-
# Returns a Point for the envelope's upper right coordinate.
|
279
|
-
def upper_right
|
280
|
-
if defined?(@upper_right)
|
281
|
-
@upper_right
|
282
|
-
else
|
283
|
-
cs = self.envelope.exterior_ring.coord_seq
|
284
|
-
@upper_right = Geos::wkt_reader_singleton.read("POINT(#{cs.get_x(2)} #{cs.get_y(2)})")
|
285
|
-
end
|
286
|
-
end
|
287
|
-
alias :ne :upper_right
|
288
|
-
alias :northeast :upper_right
|
289
|
-
|
290
|
-
# Returns a Point for the envelope's lower right coordinate.
|
291
|
-
def lower_right
|
292
|
-
if defined?(@lower_right)
|
293
|
-
@lower_right
|
294
|
-
else
|
295
|
-
cs = self.envelope.exterior_ring.coord_seq
|
296
|
-
@lower_right = Geos::wkt_reader_singleton.read("POINT(#{cs.get_x(1)} #{cs.get_y(1)})")
|
297
|
-
end
|
298
|
-
end
|
299
|
-
alias :se :lower_right
|
300
|
-
alias :southeast :lower_right
|
301
|
-
|
302
|
-
# Returns a Point for the envelope's lower left coordinate.
|
303
|
-
def lower_left
|
304
|
-
if defined?(@lower_left)
|
305
|
-
@lower_left
|
306
|
-
else
|
307
|
-
cs = self.envelope.exterior_ring.coord_seq
|
308
|
-
@lower_left = Geos::wkt_reader_singleton.read("POINT(#{cs.get_x(0)} #{cs.get_y(0)})")
|
309
|
-
end
|
310
|
-
end
|
311
|
-
alias :sw :lower_left
|
312
|
-
alias :southwest :lower_left
|
313
|
-
|
314
|
-
# Northern-most Y coordinate.
|
315
|
-
def top
|
316
|
-
if defined?(@top)
|
317
|
-
@top
|
318
|
-
else
|
319
|
-
@top = self.upper_right.y
|
320
|
-
end
|
321
|
-
end
|
322
|
-
alias :n :top
|
323
|
-
alias :north :top
|
324
|
-
|
325
|
-
# Eastern-most X coordinate.
|
326
|
-
def right
|
327
|
-
if defined?(@right)
|
328
|
-
@right
|
329
|
-
else
|
330
|
-
@right = self.upper_right.x
|
331
|
-
end
|
332
|
-
end
|
333
|
-
alias :e :right
|
334
|
-
alias :east :right
|
335
|
-
|
336
|
-
# Southern-most Y coordinate.
|
337
|
-
def bottom
|
338
|
-
if defined?(@bottom)
|
339
|
-
@bottom
|
340
|
-
else
|
341
|
-
@bottom = self.lower_left.y
|
342
|
-
end
|
343
|
-
end
|
344
|
-
alias :s :bottom
|
345
|
-
alias :south :bottom
|
346
|
-
|
347
|
-
# Western-most X coordinate.
|
348
|
-
def left
|
349
|
-
if defined?(@left)
|
350
|
-
@left
|
351
|
-
else
|
352
|
-
@left = self.lower_left.x
|
353
|
-
end
|
354
|
-
end
|
355
|
-
alias :w :left
|
356
|
-
alias :west :left
|
357
|
-
|
358
|
-
# Spits out a bounding box the way Flickr likes it. You can set the
|
359
|
-
# precision of the rounding using the :precision option. In order to
|
360
|
-
# ensure that the box is indeed a box and not merely a point, the
|
361
|
-
# southwest coordinates are floored and the northeast point ceiled.
|
362
|
-
def to_flickr_bbox(options = {})
|
363
|
-
options = {
|
364
|
-
:precision => 1
|
365
|
-
}.merge(options)
|
366
|
-
precision = 10.0 ** options[:precision]
|
367
|
-
|
368
|
-
[
|
369
|
-
(self.west * precision).floor / precision,
|
370
|
-
(self.south * precision).floor / precision,
|
371
|
-
(self.east * precision).ceil / precision,
|
372
|
-
(self.north * precision).ceil / precision
|
373
|
-
].join(',')
|
374
|
-
end
|
375
|
-
|
376
|
-
def to_geojson(options = {})
|
377
|
-
self.to_geojsonable(options).to_json
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
|
382
|
-
class CoordinateSequence
|
383
|
-
# Returns a Ruby Array of Arrays of coordinates within the
|
384
|
-
# CoordinateSequence in the form [ x, y, z ].
|
385
|
-
def to_a
|
386
|
-
(0...self.length).to_a.collect do |p|
|
387
|
-
[
|
388
|
-
self.get_x(p),
|
389
|
-
(self.dimensions >= 2 ? self.get_y(p) : nil),
|
390
|
-
(self.dimensions >= 3 && self.get_z(p) > 1.7e-306 ? self.get_z(p) : nil)
|
391
|
-
].compact
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
# Build some XmlMarkup for KML. You can set various KML options like
|
396
|
-
# tessellate, altitudeMode, etc. Use Rails/Ruby-style code and it
|
397
|
-
# will be converted automatically, i.e. :altitudeMode, not
|
398
|
-
# :altitude_mode.
|
399
|
-
def to_kml *args
|
400
|
-
xml, options = Geos::Helper.xml_options(*args)
|
401
|
-
|
402
|
-
xml.LineString(:id => options[:id]) do
|
403
|
-
xml.extrude(options[:extrude]) if options[:extrude]
|
404
|
-
xml.tessellate(options[:tessellate]) if options[:tessellate]
|
405
|
-
xml.altitudeMode(Geos::Helper.camelize(options[:altitude_mode])) if options[:altitudeMode]
|
406
|
-
xml.coordinates do
|
407
|
-
self.to_a.each do
|
408
|
-
xml << (self.to_a.join(','))
|
409
|
-
end
|
410
|
-
end
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
414
|
-
# Build some XmlMarkup for GeoRSS GML. You should include the
|
415
|
-
# appropriate georss and gml XML namespaces in your document.
|
416
|
-
def to_georss *args
|
417
|
-
xml = Geos::Helper.xml_options(*args)[0]
|
418
|
-
|
419
|
-
xml.georss(:where) do
|
420
|
-
xml.gml(:LineString) do
|
421
|
-
xml.gml(:posList) do
|
422
|
-
xml << self.to_a.collect do |p|
|
423
|
-
"#{p[1]} #{p[0]}"
|
424
|
-
end.join(' ')
|
425
|
-
end
|
426
|
-
end
|
427
|
-
end
|
428
|
-
end
|
429
|
-
|
430
|
-
# Returns a Hash suitable for converting to JSON.
|
431
|
-
#
|
432
|
-
# Options:
|
433
|
-
#
|
434
|
-
# * :encoded - enable or disable Google Maps encoding. The default is
|
435
|
-
# true.
|
436
|
-
# * :level - set the level of the Google Maps encoding algorithm.
|
437
|
-
def to_jsonable options = {}
|
438
|
-
options = {
|
439
|
-
:encoded => true,
|
440
|
-
:level => 3
|
441
|
-
}.merge options
|
442
|
-
|
443
|
-
if options[:encoded]
|
444
|
-
{
|
445
|
-
:type => 'lineString',
|
446
|
-
:encoded => true
|
447
|
-
}.merge(Geos::GoogleMaps::PolylineEncoder.encode(self.to_a, options[:level]))
|
448
|
-
else
|
449
|
-
{
|
450
|
-
:type => 'lineString',
|
451
|
-
:encoded => false,
|
452
|
-
:points => self.to_a
|
453
|
-
}
|
454
|
-
end
|
455
|
-
end
|
456
|
-
|
457
|
-
def to_geojsonable(options = {})
|
458
|
-
{
|
459
|
-
:type => 'LineString',
|
460
|
-
:coordinates => self.to_a
|
206
|
+
coords = match_data.collect(&:to_f).tap { |c|
|
207
|
+
c.reverse! unless options[:points]
|
461
208
|
}
|
209
|
+
Geos.from_wkt("POINT(#{coords.join(' ')})")
|
462
210
|
end
|
463
211
|
|
464
|
-
|
465
|
-
|
466
|
-
end
|
467
|
-
end
|
468
|
-
|
469
|
-
|
470
|
-
class Point
|
471
|
-
unless method_defined?(:y)
|
472
|
-
# Returns the Y coordinate of the Point.
|
473
|
-
def y
|
474
|
-
self.to_a[1]
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
%w{
|
479
|
-
latitude lat north south n s
|
480
|
-
}.each do |name|
|
481
|
-
self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
|
482
|
-
alias #{name} :y
|
483
|
-
EOF
|
484
|
-
end
|
485
|
-
|
486
|
-
unless method_defined?(:x)
|
487
|
-
# Returns the X coordinate of the Point.
|
488
|
-
def x
|
489
|
-
self.to_a[0]
|
490
|
-
end
|
491
|
-
end
|
492
|
-
|
493
|
-
%w{
|
494
|
-
longitude lng east west e w
|
495
|
-
}.each do |name|
|
496
|
-
self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
|
497
|
-
alias #{name} :x
|
498
|
-
EOF
|
499
|
-
end
|
500
|
-
|
501
|
-
unless method_defined?(:z)
|
502
|
-
# Returns the Z coordinate of the Point.
|
503
|
-
def z
|
504
|
-
if self.has_z?
|
505
|
-
self.to_a[2]
|
506
|
-
else
|
507
|
-
nil
|
508
|
-
end
|
509
|
-
end
|
510
|
-
end
|
511
|
-
|
512
|
-
# Returns the Point's coordinates as an Array in the following format:
|
513
|
-
#
|
514
|
-
# [ x, y, z ]
|
515
|
-
#
|
516
|
-
# The Z coordinate will only be present for Points which have a Z
|
517
|
-
# dimension.
|
518
|
-
def to_a
|
519
|
-
if defined?(@to_a)
|
520
|
-
@to_a
|
521
|
-
else
|
522
|
-
cs = self.coord_seq
|
523
|
-
@to_a = if self.has_z?
|
524
|
-
[ cs.get_x(0), cs.get_y(0), cs.get_z(0) ]
|
525
|
-
else
|
526
|
-
[ cs.get_x(0), cs.get_y(0) ]
|
527
|
-
end
|
528
|
-
end
|
529
|
-
end
|
530
|
-
|
531
|
-
# Optimize some unnecessary code away:
|
532
|
-
%w{
|
533
|
-
upper_left upper_right lower_right lower_left
|
534
|
-
ne nw se sw
|
535
|
-
northwest northeast southeast southwest
|
536
|
-
}.each do |name|
|
537
|
-
self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
|
538
|
-
def #{name}
|
539
|
-
self
|
540
|
-
end
|
541
|
-
EOF
|
542
|
-
end
|
543
|
-
|
544
|
-
# Build some XmlMarkup for KML. You can set KML options for extrude and
|
545
|
-
# altitudeMode. Use Rails/Ruby-style code and it will be converted
|
546
|
-
# appropriately, i.e. :altitude_mode, not :altitudeMode.
|
547
|
-
def to_kml *args
|
548
|
-
xml, options = Geos::Helper.xml_options(*args)
|
549
|
-
xml.Point(:id => options[:id]) do
|
550
|
-
xml.extrude(options[:extrude]) if options[:extrude]
|
551
|
-
xml.altitudeMode(Geos::Helper.camelize(options[:altitude_mode])) if options[:altitude_mode]
|
552
|
-
xml.coordinates(self.to_a.join(','))
|
553
|
-
end
|
554
|
-
end
|
555
|
-
|
556
|
-
# Build some XmlMarkup for GeoRSS. You should include the
|
557
|
-
# appropriate georss and gml XML namespaces in your document.
|
558
|
-
def to_georss *args
|
559
|
-
xml = Geos::Helper.xml_options(*args)[0]
|
560
|
-
xml.georss(:where) do
|
561
|
-
xml.gml(:Point) do
|
562
|
-
xml.gml(:pos, "#{self.lat} #{self.lng}")
|
563
|
-
end
|
564
|
-
end
|
565
|
-
end
|
566
|
-
|
567
|
-
# Returns a Hash suitable for converting to JSON.
|
568
|
-
def to_jsonable options = {}
|
569
|
-
cs = self.coord_seq
|
570
|
-
if self.has_z?
|
571
|
-
{ :type => 'point', :lat => cs.get_y(0), :lng => cs.get_x(0), :z => cs.get_z(0) }
|
572
|
-
else
|
573
|
-
{ :type => 'point', :lat => cs.get_y(0), :lng => cs.get_x(0) }
|
574
|
-
end
|
212
|
+
if options[:srid]
|
213
|
+
geom.srid = options[:srid]
|
575
214
|
end
|
576
215
|
|
577
|
-
|
578
|
-
{
|
579
|
-
:type => 'Point',
|
580
|
-
:coordinates => self.to_a
|
581
|
-
}
|
582
|
-
end
|
216
|
+
geom
|
583
217
|
end
|
584
218
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
def to_geojsonable(options = {})
|
592
|
-
self.coord_seq.to_geojsonable(options)
|
593
|
-
end
|
219
|
+
# Same as from_g_lat_lng but uses GPoints instead of GLatLngs and GBounds
|
220
|
+
# instead of GLatLngBounds. Equivalent to calling from_g_lat_lng with a
|
221
|
+
# non-false expression for the points parameter.
|
222
|
+
def self.from_g_point(geometry, options = {})
|
223
|
+
self.from_g_lat_lng(geometry, options.merge(:points => true))
|
594
224
|
end
|
595
225
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
xml, options = Geos::Helper.xml_options(*args)
|
604
|
-
|
605
|
-
xml.Polygon(:id => options[:id]) do
|
606
|
-
xml.extrude(options[:extrude]) if options[:extrude]
|
607
|
-
xml.tessellate(options[:tessellate]) if options[:tessellate]
|
608
|
-
xml.altitudeMode(Geos::Helper.camelize(options[:altitude_mode])) if options[:altitude_mode]
|
609
|
-
xml.outerBoundaryIs do
|
610
|
-
xml.LinearRing do
|
611
|
-
xml.coordinates do
|
612
|
-
xml << self.exterior_ring.coord_seq.to_a.collect do |p|
|
613
|
-
p.join(',')
|
614
|
-
end.join(' ')
|
615
|
-
end
|
616
|
-
end
|
617
|
-
end
|
618
|
-
(0...self.num_interior_rings).to_a.each do |n|
|
619
|
-
xml.innerBoundaryIs do
|
620
|
-
xml.LinearRing do
|
621
|
-
xml.coordinates do
|
622
|
-
xml << self.interior_ring_n(n).coord_seq.to_a.collect do |p|
|
623
|
-
p.join(',')
|
624
|
-
end.join(' ')
|
625
|
-
end
|
626
|
-
end
|
627
|
-
end
|
628
|
-
end if options[:interior_rings] && self.num_interior_rings > 0
|
629
|
-
end
|
630
|
-
end
|
631
|
-
|
632
|
-
# Build some XmlMarkup for GeoRSS. You should include the
|
633
|
-
# appropriate georss and gml XML namespaces in your document.
|
634
|
-
def to_georss *args
|
635
|
-
xml = Geos::Helper.xml_options(*args)[0]
|
636
|
-
|
637
|
-
xml.georss(:where) do
|
638
|
-
xml.gml(:Polygon) do
|
639
|
-
xml.gml(:exterior) do
|
640
|
-
xml.gml(:LinearRing) do
|
641
|
-
xml.gml(:posList) do
|
642
|
-
xml << self.exterior_ring.coord_seq.to_a.collect do |p|
|
643
|
-
"#{p[1]} #{p[0]}"
|
644
|
-
end.join(' ')
|
645
|
-
end
|
646
|
-
end
|
647
|
-
end
|
648
|
-
end
|
649
|
-
end
|
650
|
-
end
|
651
|
-
|
652
|
-
# Returns a Hash suitable for converting to JSON.
|
653
|
-
#
|
654
|
-
# Options:
|
655
|
-
#
|
656
|
-
# * :encoded - enable or disable Google Maps encoding. The default is
|
657
|
-
# true.
|
658
|
-
# * :level - set the level of the Google Maps encoding algorithm.
|
659
|
-
# * :interior_rings - add interior rings to the output. The default
|
660
|
-
# is false.
|
661
|
-
# * :style_options - any style options you want to pass along in the
|
662
|
-
# JSON. These options will be automatically camelized into
|
663
|
-
# Javascripty code.
|
664
|
-
def to_jsonable options = {}
|
665
|
-
options = {
|
666
|
-
:encoded => true,
|
667
|
-
:level => 3,
|
668
|
-
:interior_rings => false
|
669
|
-
}.merge options
|
670
|
-
|
671
|
-
style_options = Hash.new
|
672
|
-
if options[:style_options] && !options[:style_options].empty?
|
673
|
-
options[:style_options].each do |k, v|
|
674
|
-
style_options[Geos::Helper.camelize(k.to_s)] = v
|
675
|
-
end
|
676
|
-
end
|
677
|
-
|
678
|
-
if options[:encoded]
|
679
|
-
ret = {
|
680
|
-
:type => 'polygon',
|
681
|
-
:encoded => true,
|
682
|
-
:polylines => [ Geos::GoogleMaps::PolylineEncoder.encode(
|
683
|
-
self.exterior_ring.coord_seq.to_a,
|
684
|
-
options[:level]
|
685
|
-
).merge(:bounds => {
|
686
|
-
:sw => self.lower_left.to_a,
|
687
|
-
:ne => self.upper_right.to_a
|
688
|
-
})
|
689
|
-
],
|
690
|
-
:options => style_options
|
691
|
-
}
|
692
|
-
|
693
|
-
if options[:interior_rings] && self.num_interior_rings > 0
|
694
|
-
(0..(self.num_interior_rings) - 1).to_a.each do |n|
|
695
|
-
ret[:polylines] << Geos::GoogleMaps::PolylineEncoder.encode(
|
696
|
-
self.interior_ring_n(n).coord_seq.to_a,
|
697
|
-
options[:level]
|
698
|
-
)
|
699
|
-
end
|
700
|
-
end
|
701
|
-
ret
|
226
|
+
# Creates a Geometry from a PostGIS-style BOX string.
|
227
|
+
def self.from_box2d(geometry_or_match_data)
|
228
|
+
match_data = case geometry_or_match_data
|
229
|
+
when MatchData
|
230
|
+
geometry_or_match_data.captures
|
231
|
+
when REGEXP_BOX2D
|
232
|
+
$~.captures
|
702
233
|
else
|
703
|
-
|
704
|
-
:type => 'polygon',
|
705
|
-
:encoded => false,
|
706
|
-
:polylines => [{
|
707
|
-
:points => self.exterior_ring.coord_seq.to_a,
|
708
|
-
:bounds => {
|
709
|
-
:sw => self.lower_left.to_a,
|
710
|
-
:ne => self.upper_right.to_a
|
711
|
-
}
|
712
|
-
}]
|
713
|
-
}
|
714
|
-
if options[:interior_rings] && self.num_interior_rings > 0
|
715
|
-
(0..(self.num_interior_rings) - 1).to_a.each do |n|
|
716
|
-
ret[:polylines] << {
|
717
|
-
:points => self.interior_ring_n(n).coord_seq.to_a
|
718
|
-
}
|
719
|
-
end
|
720
|
-
end
|
721
|
-
ret
|
722
|
-
end
|
234
|
+
raise "Invalid BOX2D"
|
723
235
|
end
|
724
236
|
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
def to_geojsonable(options = {})
|
730
|
-
options = {
|
731
|
-
:interior_rings => true
|
732
|
-
}.merge(options)
|
733
|
-
|
734
|
-
ret = {
|
735
|
-
:type => 'Polygon',
|
736
|
-
:coordinates => [ self.exterior_ring.coord_seq.to_a ]
|
737
|
-
}
|
237
|
+
coords = []
|
238
|
+
match_data.compact.each_slice(2) { |f|
|
239
|
+
coords << f.collect(&:to_f)
|
240
|
+
}
|
738
241
|
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
end
|
744
|
-
|
745
|
-
ret
|
746
|
-
end
|
747
|
-
end
|
748
|
-
|
749
|
-
|
750
|
-
class GeometryCollection
|
751
|
-
if !GeometryCollection.included_modules.include?(Enumerable)
|
752
|
-
include Enumerable
|
753
|
-
|
754
|
-
# Iterates the collection through the given block.
|
755
|
-
def each
|
756
|
-
self.num_geometries.times do |n|
|
757
|
-
yield self.get_geometry_n(n)
|
758
|
-
end
|
759
|
-
nil
|
760
|
-
end
|
761
|
-
|
762
|
-
# Returns the nth geometry from the collection.
|
763
|
-
def [](*args)
|
764
|
-
self.to_a[*args]
|
765
|
-
end
|
766
|
-
alias :slice :[]
|
767
|
-
end
|
768
|
-
|
769
|
-
# Returns the last geometry from the collection.
|
770
|
-
def last
|
771
|
-
self.get_geometry_n(self.num_geometries - 1) if self.num_geometries > 0
|
772
|
-
end
|
773
|
-
|
774
|
-
# Returns a Hash suitable for converting to JSON.
|
775
|
-
def to_jsonable options = {}
|
776
|
-
self.collect do |p|
|
777
|
-
p.to_jsonable options
|
778
|
-
end
|
779
|
-
end
|
780
|
-
|
781
|
-
# Build some XmlMarkup for KML.
|
782
|
-
def to_kml *args
|
783
|
-
self.collect do |p|
|
784
|
-
p.to_kml(*args)
|
785
|
-
end
|
786
|
-
end
|
787
|
-
|
788
|
-
# Build some XmlMarkup for GeoRSS. Since GeoRSS is pretty trimed down,
|
789
|
-
# we just take the entire collection and use the exterior_ring as
|
790
|
-
# a Polygon. Not to bright, mind you, but until GeoRSS stops with the
|
791
|
-
# suck, what are we to do. You should include the appropriate georss
|
792
|
-
# and gml XML namespaces in your document.
|
793
|
-
def to_georss *args
|
794
|
-
self.exterior_ring.to_georss(*args)
|
795
|
-
end
|
796
|
-
|
797
|
-
def to_geojsonable(options = {})
|
798
|
-
{
|
799
|
-
:type => 'GeometryCollection',
|
800
|
-
:geometries => self.to_a.collect { |g| g.to_geojsonable(options) }
|
801
|
-
}
|
802
|
-
end
|
803
|
-
end
|
804
|
-
|
805
|
-
class MultiPolygon < GeometryCollection
|
806
|
-
def to_geojsonable(options = {})
|
807
|
-
options = {
|
808
|
-
:interior_rings => true
|
809
|
-
}.merge(options)
|
810
|
-
|
811
|
-
{
|
812
|
-
:type => 'MultiPolygon',
|
813
|
-
:coordinates => self.to_a.collect { |polygon|
|
814
|
-
coords = [ polygon.exterior_ring.coord_seq.to_a ]
|
815
|
-
|
816
|
-
if options[:interior_rings] && polygon.num_interior_rings > 0
|
817
|
-
coords.concat polygon.interior_rings.collect { |r|
|
818
|
-
r.coord_seq.to_a
|
819
|
-
}
|
820
|
-
end
|
821
|
-
|
822
|
-
coords
|
823
|
-
}
|
824
|
-
}
|
825
|
-
end
|
826
|
-
end
|
827
|
-
|
828
|
-
class MultiLineString < GeometryCollection
|
829
|
-
def to_geojsonable(options = {})
|
830
|
-
{
|
831
|
-
:type => 'MultiLineString',
|
832
|
-
:coordinates => self.to_a.collect { |linestring|
|
833
|
-
linestring.coord_seq.to_a
|
834
|
-
}
|
835
|
-
}
|
836
|
-
end
|
837
|
-
end
|
838
|
-
|
839
|
-
class MultiPoint < GeometryCollection
|
840
|
-
def to_geojsonable(options = {})
|
841
|
-
{
|
842
|
-
:type => 'MultiPoint',
|
843
|
-
:coordinates => self.to_a.collect { |point|
|
844
|
-
point.to_a
|
845
|
-
}
|
846
|
-
}
|
847
|
-
end
|
242
|
+
Geos.from_wkt("LINESTRING(%s, %s)" % [
|
243
|
+
coords[0].join(' '),
|
244
|
+
coords[1].join(' ')
|
245
|
+
]).envelope
|
848
246
|
end
|
849
247
|
end
|
248
|
+
|