geos-extensions 0.2.2 → 0.3.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.
- 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
|
+
|