border_patrol 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +31 -0
- data/README.markdown +40 -0
- data/Rakefile +15 -0
- data/border_patrol.gemspec +28 -0
- data/lib/VERSION +1 -0
- data/lib/border_patrol.rb +28 -0
- data/lib/border_patrol/polygon.rb +73 -0
- data/lib/border_patrol/region.rb +12 -0
- data/spec/lib/border_patrol/polygon_spec.rb +139 -0
- data/spec/lib/border_patrol/region_spec.rb +46 -0
- data/spec/lib/border_patrol_spec.rb +60 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/colorado-test.kml +37 -0
- data/spec/support/formatters/compact_progress_bar_formatter.rb +133 -0
- data/spec/support/multi-polygon-test.kml +100 -0
- metadata +154 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
---
|
2
|
+
dependencies:
|
3
|
+
rake:
|
4
|
+
group:
|
5
|
+
- :development
|
6
|
+
version: = 0.8.7
|
7
|
+
rspec:
|
8
|
+
group:
|
9
|
+
- :development
|
10
|
+
version: = 1.3.0
|
11
|
+
progressbar:
|
12
|
+
group:
|
13
|
+
- :development
|
14
|
+
version: ">= 0"
|
15
|
+
nokogiri:
|
16
|
+
group:
|
17
|
+
- :default
|
18
|
+
version: = 1.4.3.1
|
19
|
+
specs:
|
20
|
+
- rake:
|
21
|
+
version: 0.8.7
|
22
|
+
- nokogiri:
|
23
|
+
version: 1.4.3.1
|
24
|
+
- progressbar:
|
25
|
+
version: 0.9.0
|
26
|
+
- rspec:
|
27
|
+
version: 1.3.0
|
28
|
+
hash: cbbadd85a1348941a32f127e7ae81222e0cfb8bc
|
29
|
+
sources:
|
30
|
+
- Rubygems:
|
31
|
+
uri: http://gemcutter.org
|
data/README.markdown
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# BorderPatrol
|
2
|
+
|
3
|
+
BorderPatrol lets you import a KML file and then check if points are inside or outside the polygons the file defines.
|
4
|
+
|
5
|
+
The KML file may have multiple polygons defined, google maps is a good source.
|
6
|
+
|
7
|
+
## Examples
|
8
|
+
|
9
|
+
An example KML file can be found here:
|
10
|
+
http://maps.google.com/maps/ms?ie=UTF8&hl=en&msa=0&ll=38.814031,-103.743896&spn=9.600749,16.248779&z=7&msid=110523771099674876521.00049301d20252132a92c&output=kml
|
11
|
+
|
12
|
+
To test if a point is in the region you can either pass a class that responds to `x` and `y` (like the provided BorderPatrol::Point class) or just pass a longitude latitude pair.
|
13
|
+
|
14
|
+
region = BorderPatrol.parse_kml(File.read('spec/support/colorado-test.kml'))
|
15
|
+
denver = BorderPatrol::Point.new(-105, 39.75)
|
16
|
+
region.contains_point?(denver) # true
|
17
|
+
region.contains_point?(-105, 39.75) # also true!
|
18
|
+
san_francisco = BorderPatrol::Point.new(-122.5, 37.75)
|
19
|
+
region.contains_point?(san_francisco) # false
|
20
|
+
region.contains_point?(-122.5, 37.75) # also false!
|
21
|
+
|
22
|
+
If you want to use your own point class, just define `x` and `y` as methods that correspond to `longitude` and `latitude`.
|
23
|
+
|
24
|
+
## Pro Tip
|
25
|
+
|
26
|
+
You can make KML files easily on Google Maps by clicking "My Maps", drawing shapes and saving the map. Just copy the share link and add "&output=kml" to download the file.g
|
27
|
+
|
28
|
+
## Dependencies
|
29
|
+
|
30
|
+
* Nokogiri
|
31
|
+
|
32
|
+
## Known Issues
|
33
|
+
|
34
|
+
Polygons across the international date line don't work.
|
35
|
+
|
36
|
+
## Acknowledgements
|
37
|
+
|
38
|
+
http://jakescruggs.blogspot.com/2009/07/point-inside-polygon-in-ruby.html for evaluating the algorithm.
|
39
|
+
|
40
|
+
http://github.com/nofxx/georuby/ for providing the bounding box code.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'rake'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
|
9
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
10
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
spec.libs << 'spec'
|
12
|
+
spec.spec_opts = ["--options", "spec/spec.opts"]
|
13
|
+
end
|
14
|
+
|
15
|
+
task :default => :spec
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "border_patrol"
|
7
|
+
s.version = File.read("lib/VERSION").strip
|
8
|
+
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
s.rubygems_version = "1.3.7"
|
11
|
+
|
12
|
+
s.authors = ["Zach Brock", "Matt Wilson"]
|
13
|
+
s.email = "eng@squareup.com"
|
14
|
+
|
15
|
+
s.date = "2010-10-20"
|
16
|
+
s.description = "Lets you import a KML file and then check if points are inside or outside the polygons the file defines."
|
17
|
+
s.summary = "Import and query KML regions"
|
18
|
+
s.homepage = "http://github.com/square/border_patrol"
|
19
|
+
|
20
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
21
|
+
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
root_files = %w(border_patrol.gemspec Rakefile README.markdown .gitignore Gemfile Gemfile.lock)
|
24
|
+
s.files = Dir['{lib,spec}/**/*'] + root_files
|
25
|
+
s.test_files = Dir['spec/**/*']
|
26
|
+
|
27
|
+
s.add_bundler_dependencies
|
28
|
+
end
|
data/lib/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BorderPatrol
|
2
|
+
class InsufficientPointsToActuallyFormAPolygonError < ArgumentError; end
|
3
|
+
class Point < Struct.new(:x, :y); end
|
4
|
+
|
5
|
+
def self.parse_kml(string)
|
6
|
+
doc = Nokogiri::XML(string)
|
7
|
+
polygons = doc.xpath('//kml:Polygon', 'kml' => 'http://earth.google.com/kml/2.2').map do |polygon_kml|
|
8
|
+
parse_kml_polygon_data(polygon_kml.to_s)
|
9
|
+
end
|
10
|
+
BorderPatrol::Region.new(polygons)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def self.parse_kml_polygon_data(string)
|
15
|
+
doc = Nokogiri::XML(string)
|
16
|
+
coordinates = doc.xpath("//coordinates").text.strip.split("\n")
|
17
|
+
points = coordinates.map do |coord|
|
18
|
+
x, y, z = coord.strip.split(',')
|
19
|
+
BorderPatrol::Point.new(x.to_f, y.to_f)
|
20
|
+
end
|
21
|
+
BorderPatrol::Polygon.new(points)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'set'
|
26
|
+
require 'nokogiri'
|
27
|
+
require 'border_patrol/polygon.rb'
|
28
|
+
require 'border_patrol/region.rb'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module BorderPatrol
|
2
|
+
class Polygon < Array
|
3
|
+
def initialize(* args)
|
4
|
+
args.flatten!
|
5
|
+
args.uniq!
|
6
|
+
raise InsufficientPointsToActuallyFormAPolygonError unless args.size > 2
|
7
|
+
super(args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
# Do we have the right number of points?
|
12
|
+
return false unless other.size == size
|
13
|
+
|
14
|
+
# Are the points in the right order?
|
15
|
+
first, second = self.first(2)
|
16
|
+
index = other.index(first)
|
17
|
+
return false unless index
|
18
|
+
direction = (other[index-1] == second) ? -1 : 1
|
19
|
+
# Check if the two polygons have the same edges and the same points
|
20
|
+
# i.e. [point1, point2, point3] is the same as [point2, point3, point1] is the same as [point3, point2, point1]
|
21
|
+
each do |i|
|
22
|
+
return false unless i == other[index]
|
23
|
+
index = index + direction
|
24
|
+
index = 0 if index == size
|
25
|
+
end
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Quick and dirty hash function
|
30
|
+
def hash
|
31
|
+
inject(0) { |sum, point| sum += point.x + point.y }
|
32
|
+
end
|
33
|
+
|
34
|
+
def contains_point?(point)
|
35
|
+
return false unless inside_bounding_box?(point)
|
36
|
+
c = false
|
37
|
+
i = -1
|
38
|
+
j = self.size - 1
|
39
|
+
while (i += 1) < self.size
|
40
|
+
if ((self[i].y <= point.y && point.y < self[j].y) ||
|
41
|
+
(self[j].y <= point.y && point.y < self[i].y))
|
42
|
+
if (point.x < (self[j].x - self[i].x) * (point.y - self[i].y) /
|
43
|
+
(self[j].y - self[i].y) + self[i].x)
|
44
|
+
c = !c
|
45
|
+
end
|
46
|
+
end
|
47
|
+
j = i
|
48
|
+
end
|
49
|
+
return c
|
50
|
+
end
|
51
|
+
|
52
|
+
def inside_bounding_box?(point)
|
53
|
+
bb_point_1, bb_point_2 = bounding_box
|
54
|
+
max_x = [bb_point_1.x, bb_point_2.x].max
|
55
|
+
max_y = [bb_point_1.y, bb_point_2.y].max
|
56
|
+
min_x = [bb_point_1.x, bb_point_2.x].min
|
57
|
+
min_y = [bb_point_1.y, bb_point_2.y].min
|
58
|
+
|
59
|
+
!(point.x < min_x || point.x > max_x || point.y < min_y || point.y > max_y)
|
60
|
+
end
|
61
|
+
|
62
|
+
def bounding_box
|
63
|
+
max_x, min_x, max_y, min_y = -Float::MAX, Float::MAX, -Float::MAX, Float::MAX
|
64
|
+
each do |point|
|
65
|
+
max_y = point.y if point.y > max_y
|
66
|
+
min_y = point.y if point.y < min_y
|
67
|
+
max_x = point.x if point.x > max_x
|
68
|
+
min_x = point.x if point.x < min_x
|
69
|
+
end
|
70
|
+
[Point.new(min_x, max_y), Point.new(max_x, min_y)]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module BorderPatrol
|
2
|
+
class Region < Set
|
3
|
+
def contains_point?(*point)
|
4
|
+
point = case point.length
|
5
|
+
when 1 then point.first
|
6
|
+
when 2 then BorderPatrol::Point.new(point[0],point[1])
|
7
|
+
else raise ArgumentError, "#{point} is invalid. Arguments can either be an object, or a longitude,lattitude pair."
|
8
|
+
end
|
9
|
+
any? { |polygon| polygon.contains_point?(point) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BorderPatrol::Polygon do
|
4
|
+
describe "==" do
|
5
|
+
it "is true if polygons are congruent" do
|
6
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0)]
|
7
|
+
poly1 = BorderPatrol::Polygon.new(points)
|
8
|
+
poly2 = BorderPatrol::Polygon.new(points.unshift(points.pop))
|
9
|
+
|
10
|
+
poly1.should == poly2
|
11
|
+
poly2.should == poly1
|
12
|
+
poly3 = BorderPatrol::Polygon.new(points.reverse)
|
13
|
+
poly1.should == poly3
|
14
|
+
poly3.should == poly1
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
it "cares about order of points" do
|
19
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(5,5), BorderPatrol::Point.new(0, 0)]
|
20
|
+
poly1 = BorderPatrol::Polygon.new(points)
|
21
|
+
points = [BorderPatrol::Point.new(5,5), BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(0, 0), BorderPatrol::Point.new(3, 4)]
|
22
|
+
poly2 = BorderPatrol::Polygon.new(points)
|
23
|
+
|
24
|
+
poly1.should_not == poly2
|
25
|
+
poly2.should_not == poly1
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is false if one polygon is a subset" do
|
30
|
+
poly1 = BorderPatrol::Polygon.new(BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0))
|
31
|
+
poly2 = BorderPatrol::Polygon.new(BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0), BorderPatrol::Point.new(4, 4))
|
32
|
+
poly2.should_not == poly1
|
33
|
+
poly1.should_not == poly2
|
34
|
+
end
|
35
|
+
|
36
|
+
it "is false if the polygons are not congruent" do
|
37
|
+
poly1 = BorderPatrol::Polygon.new(BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0))
|
38
|
+
poly2 = BorderPatrol::Polygon.new(BorderPatrol::Point.new(2, 1), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0))
|
39
|
+
poly2.should_not == poly1
|
40
|
+
poly1.should_not == poly2
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#initialize" do
|
45
|
+
it "stores a list of points" do
|
46
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0)]
|
47
|
+
polygon = BorderPatrol::Polygon.new(points)
|
48
|
+
points.each do |point|
|
49
|
+
polygon.should include point
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "can be instantiated with a arbitrary argument list" do
|
54
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0)]
|
55
|
+
poly1 = BorderPatrol::Polygon.new(*points)
|
56
|
+
poly2 = BorderPatrol::Polygon.new(points)
|
57
|
+
poly1.should == poly2
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises if less than 3 points are given" do
|
61
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(2, 3)]
|
62
|
+
expect { BorderPatrol::Polygon.new(points) }.to raise_exception(BorderPatrol::InsufficientPointsToActuallyFormAPolygonError)
|
63
|
+
points = [BorderPatrol::Point.new(1, 2)]
|
64
|
+
expect { BorderPatrol::Polygon.new(points) }.to raise_exception(BorderPatrol::InsufficientPointsToActuallyFormAPolygonError)
|
65
|
+
points = []
|
66
|
+
expect { BorderPatrol::Polygon.new(points) }.to raise_exception(BorderPatrol::InsufficientPointsToActuallyFormAPolygonError)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "doesn't store duplicated points" do
|
70
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0)]
|
71
|
+
duplicate_point = [BorderPatrol::Point.new(1, 2)]
|
72
|
+
polygon = BorderPatrol::Polygon.new(points + duplicate_point)
|
73
|
+
polygon.size.should == 3
|
74
|
+
points.each do |point|
|
75
|
+
polygon.should include point
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#bounding_box" do
|
81
|
+
it "returns the (max top, max left), (max bottom, max right) as points" do
|
82
|
+
points = [BorderPatrol::Point.new(-1, 3), BorderPatrol::Point.new(4, -3), BorderPatrol::Point.new(10, 4), BorderPatrol::Point.new(0, 12)]
|
83
|
+
polygon = BorderPatrol::Polygon.new(points)
|
84
|
+
polygon.bounding_box.should == [BorderPatrol::Point.new(-1, 12), BorderPatrol::Point.new(10, -3)]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#contains_point?" do
|
89
|
+
before do
|
90
|
+
points = [BorderPatrol::Point.new(-10, 0), BorderPatrol::Point.new(10, 0), BorderPatrol::Point.new(0, 10)]
|
91
|
+
@polygon = BorderPatrol::Polygon.new(points)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "is true if the point is in the polygon" do
|
95
|
+
@polygon.contains_point?(BorderPatrol::Point.new(0.5, 0.5)).should be_true
|
96
|
+
@polygon.contains_point?(BorderPatrol::Point.new(0, 5)).should be_true
|
97
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-1, 3)).should be_true
|
98
|
+
end
|
99
|
+
|
100
|
+
it "does not include points on the lines with slopes between vertices" do
|
101
|
+
@polygon.contains_point?(BorderPatrol::Point.new(5.0, 5.0)).should be_false
|
102
|
+
@polygon.contains_point?(BorderPatrol::Point.new(4.999999, 4.9999999)).should be_true
|
103
|
+
@polygon.contains_point?(BorderPatrol::Point.new(0, 0)).should be_true
|
104
|
+
@polygon.contains_point?(BorderPatrol::Point.new(0.000001, 0.000001)).should be_true
|
105
|
+
end
|
106
|
+
|
107
|
+
it "includes points at the vertices" do
|
108
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-10, 0)).should be_true
|
109
|
+
end
|
110
|
+
|
111
|
+
it "is false if the point is outside of the polygon" do
|
112
|
+
@polygon.contains_point?(BorderPatrol::Point.new(9, 5)).should be_false
|
113
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-5, 8)).should be_false
|
114
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-10, -1)).should be_false
|
115
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-20, -20)).should be_false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#inside_bounding_box?" do
|
120
|
+
before do
|
121
|
+
points = [BorderPatrol::Point.new(-10, 0), BorderPatrol::Point.new(10, 0), BorderPatrol::Point.new(0, 10)]
|
122
|
+
@polygon = BorderPatrol::Polygon.new(points)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "is false if it is outside the bounding box" do
|
126
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(-10, -1)).should be_false
|
127
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(-20, -20)).should be_false
|
128
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(1, 20)).should be_false
|
129
|
+
end
|
130
|
+
|
131
|
+
it "returns true if it is inside the bounding box" do
|
132
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(9, 5)).should be_true
|
133
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(-5, 8)).should be_true
|
134
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(1, 1)).should be_true
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BorderPatrol::Region do
|
4
|
+
it "is a Set" do
|
5
|
+
BorderPatrol::Region.new.should be_a Set
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#contains_point?" do
|
9
|
+
def point
|
10
|
+
|
11
|
+
end
|
12
|
+
def polygon(start=0)
|
13
|
+
BorderPatrol::Polygon.new(
|
14
|
+
BorderPatrol::Point.new(start,start),
|
15
|
+
BorderPatrol::Point.new(start + 10, start),
|
16
|
+
BorderPatrol::Point.new(start + 10, start + 10),
|
17
|
+
BorderPatrol::Point.new(start, start + 10))
|
18
|
+
end
|
19
|
+
subject { BorderPatrol::Region.new(@polygons) }
|
20
|
+
|
21
|
+
it "raises an argument error if contains_point? takes more than 3 arguments" do
|
22
|
+
expect { subject.contains_point? }.to raise_exception ArgumentError
|
23
|
+
expect { subject.contains_point?(1,2,3) }.to raise_exception ArgumentError
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns true if any polygon contains the point" do
|
27
|
+
point = BorderPatrol::Point.new(1,2)
|
28
|
+
@polygons = [polygon, polygon(30)]
|
29
|
+
|
30
|
+
subject.contains_point?(point).should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns false if no polygons contain the point" do
|
34
|
+
point = BorderPatrol::Point.new(-1,-2)
|
35
|
+
@polygons = [polygon, polygon(30)]
|
36
|
+
|
37
|
+
subject.contains_point?(point).should be_false
|
38
|
+
end
|
39
|
+
|
40
|
+
it "transforms (x,y) coordinates passed in into a point" do
|
41
|
+
@polygons = [polygon, polygon(30)]
|
42
|
+
|
43
|
+
subject.contains_point?(1,2).should be_true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BorderPatrol do
|
4
|
+
describe ".parse_kml" do
|
5
|
+
it "returns a BorderPatrol::Region containing a BorderPatrol::Polygon for each polygon in the KML file" do
|
6
|
+
kml_data = File.read("#{File.dirname(__FILE__)}/../support/multi-polygon-test.kml")
|
7
|
+
region = BorderPatrol.parse_kml(kml_data)
|
8
|
+
region.length.should == 3
|
9
|
+
region.each {|p| p.should be_a BorderPatrol::Polygon}
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when there is only one polygon" do
|
13
|
+
it "returns a region containing a single polygon" do
|
14
|
+
kml_data = File.read("#{File.dirname(__FILE__)}/../support/colorado-test.kml")
|
15
|
+
region = BorderPatrol.parse_kml(kml_data)
|
16
|
+
region.length.should == 1
|
17
|
+
region.each {|p| p.should be_a BorderPatrol::Polygon}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ".parse_kml_polygon_data" do
|
23
|
+
it "returns a BorderPatrol::Polygon with the points from the kml data in the correct order" do
|
24
|
+
kml = <<-EOM
|
25
|
+
<Polygon>
|
26
|
+
<outerBoundaryIs>
|
27
|
+
<LinearRing>
|
28
|
+
<tessellate>1</tessellate>
|
29
|
+
<coordinates>
|
30
|
+
-10,25,0.000000
|
31
|
+
-1,30,0.000000
|
32
|
+
10,1,0.000000
|
33
|
+
0, -5,000000
|
34
|
+
-10,25,0.000000
|
35
|
+
</coordinates>
|
36
|
+
</LinearRing>
|
37
|
+
</outerBoundaryIs>
|
38
|
+
</Polygon>
|
39
|
+
EOM
|
40
|
+
polygon = BorderPatrol.parse_kml_polygon_data(kml)
|
41
|
+
polygon.should == BorderPatrol::Polygon.new(BorderPatrol::Point.new(-10, 25), BorderPatrol::Point.new(-1, 30), BorderPatrol::Point.new(10, 1), BorderPatrol::Point.new(0, -5))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe BorderPatrol::Point do
|
46
|
+
describe "==" do
|
47
|
+
it "is true if both points contain the same values" do
|
48
|
+
BorderPatrol::Point.new(1,2).should == BorderPatrol::Point.new(1,2)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "is true if one point contains floats and one contains integers" do
|
52
|
+
BorderPatrol::Point.new(1,2.0).should == BorderPatrol::Point.new(1.0,2)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "is false if the points contain different values" do
|
56
|
+
BorderPatrol::Point.new(1,3).should_not == BorderPatrol::Point.new(1.0,2)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
base_dir = File.expand_path("#{File.dirname(__FILE__)}/..")
|
8
|
+
app_dirs = ['lib', 'lib/kaml_pen']
|
9
|
+
|
10
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
|
11
|
+
|
12
|
+
app_dirs.each do |app_dir|
|
13
|
+
Dir["#{base_dir}/spec/#{app_dir}/*.rb"].each do |f|
|
14
|
+
f.sub!('/spec','')
|
15
|
+
f.sub!('_spec','')
|
16
|
+
require f
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Spec::Runner.configure do |config|
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<kml xmlns="http://earth.google.com/kml/2.2">
|
3
|
+
<Document>
|
4
|
+
<name>Colorado</name>
|
5
|
+
<description><![CDATA[]]></description>
|
6
|
+
<Style id="style1">
|
7
|
+
<LineStyle>
|
8
|
+
<color>40000000</color>
|
9
|
+
<width>3</width>
|
10
|
+
</LineStyle>
|
11
|
+
<PolyStyle>
|
12
|
+
<color>73FF0000</color>
|
13
|
+
<fill>1</fill>
|
14
|
+
<outline>1</outline>
|
15
|
+
</PolyStyle>
|
16
|
+
</Style>
|
17
|
+
<Placemark>
|
18
|
+
<name>Shape 1</name>
|
19
|
+
<description><![CDATA[]]></description>
|
20
|
+
<styleUrl>#style1</styleUrl>
|
21
|
+
<Polygon>
|
22
|
+
<outerBoundaryIs>
|
23
|
+
<LinearRing>
|
24
|
+
<tessellate>1</tessellate>
|
25
|
+
<coordinates>
|
26
|
+
-109.050293,41.008923,0.000000
|
27
|
+
-102.057495,41.004776,0.000000
|
28
|
+
-102.046509,36.993778,0.000000
|
29
|
+
-109.044800,36.998165,0.000000
|
30
|
+
-109.050293,41.008923,0.000000
|
31
|
+
</coordinates>
|
32
|
+
</LinearRing>
|
33
|
+
</outerBoundaryIs>
|
34
|
+
</Polygon>
|
35
|
+
</Placemark>
|
36
|
+
</Document>
|
37
|
+
</kml>
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Originally by Nicholas A. Evans. See LICENSE.txt at
|
2
|
+
# http://gist.github.com/275257
|
3
|
+
#
|
4
|
+
# Note: includes modifications from the original to use '=' for the progress
|
5
|
+
# bar mark and keep the cursor from hopping all around during the animation.
|
6
|
+
#
|
7
|
+
# This version is compatible with RSpec 1.2.9 and ProgressBar 0.9.0.
|
8
|
+
|
9
|
+
require 'spec/runner/formatter/base_text_formatter'
|
10
|
+
require 'progressbar'
|
11
|
+
|
12
|
+
module Spec
|
13
|
+
module Runner
|
14
|
+
module Formatter
|
15
|
+
class CompactProgressBarFormatter < BaseTextFormatter
|
16
|
+
# Threshold for slow specs, in seconds.
|
17
|
+
# Anything that takes longer than this will be printed out
|
18
|
+
THRESHOLD = 1.0 unless defined?(THRESHOLD)
|
19
|
+
|
20
|
+
attr_reader :total, :current
|
21
|
+
|
22
|
+
def start(example_count)
|
23
|
+
@current = 0
|
24
|
+
@total = example_count
|
25
|
+
@error_state = :all_passing
|
26
|
+
@pbar = ProgressBar.new("#{example_count} examples", example_count, output)
|
27
|
+
@pbar.instance_variable_set("@bar_mark", "=")
|
28
|
+
end
|
29
|
+
|
30
|
+
def example_started(example)
|
31
|
+
super
|
32
|
+
@start_time = Time.now
|
33
|
+
end
|
34
|
+
|
35
|
+
def example_passed(example)
|
36
|
+
print_warning_if_slow(example_group.description,
|
37
|
+
example.description,
|
38
|
+
Time.now - @start_time)
|
39
|
+
increment
|
40
|
+
end
|
41
|
+
|
42
|
+
def example_pending(example, message, deprecated_pending_location=nil)
|
43
|
+
immediately_dump_pending(example.description, message, pending_caller)
|
44
|
+
mark_error_state_pending
|
45
|
+
increment
|
46
|
+
end
|
47
|
+
|
48
|
+
def example_failed(example, counter, failure)
|
49
|
+
immediately_dump_failure(counter, failure)
|
50
|
+
mark_error_state_failed
|
51
|
+
increment
|
52
|
+
end
|
53
|
+
|
54
|
+
def start_dump
|
55
|
+
with_color do
|
56
|
+
@pbar.finish
|
57
|
+
end
|
58
|
+
output.flush
|
59
|
+
end
|
60
|
+
|
61
|
+
def dump_failure(*args)
|
62
|
+
# no-op; we summarized failures as we were running
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(sym, *args)
|
66
|
+
# ignore
|
67
|
+
end
|
68
|
+
|
69
|
+
# Adapted from BaseTextFormatter#dump_failure
|
70
|
+
def immediately_dump_failure(counter, failure)
|
71
|
+
erase_current_line
|
72
|
+
output.print "#{counter.to_s}) "
|
73
|
+
output.puts colorize_failure("#{failure.header}\n#{failure.exception.message}", failure)
|
74
|
+
output.puts format_backtrace(failure.exception.backtrace)
|
75
|
+
output.puts
|
76
|
+
end
|
77
|
+
|
78
|
+
# Adapted from BaseTextFormatter#dump_pending
|
79
|
+
def immediately_dump_pending(desc, msg, called_from)
|
80
|
+
erase_current_line
|
81
|
+
output.puts yellow("PENDING SPEC:") + " #{desc} (#{msg})"
|
82
|
+
output.puts " Called from #{called_from}" if called_from
|
83
|
+
end
|
84
|
+
|
85
|
+
def increment
|
86
|
+
with_color do
|
87
|
+
@current += 1
|
88
|
+
# Since we're constantly erasing the line, make sure the progress is
|
89
|
+
# printed even when the bar hasn't changed
|
90
|
+
@pbar.instance_variable_set("@previous", 0)
|
91
|
+
@pbar.instance_variable_set("@title", " #{current}/#{total}")
|
92
|
+
@pbar.inc
|
93
|
+
end
|
94
|
+
output.flush
|
95
|
+
end
|
96
|
+
|
97
|
+
ERROR_STATE_COLORS = {
|
98
|
+
:all_passing => "\e[32m", # green
|
99
|
+
:some_pending => "\e[33m", # yellow
|
100
|
+
:some_failed => "\e[31m", # red
|
101
|
+
} unless defined?(ERROR_STATE_COLORS)
|
102
|
+
|
103
|
+
def with_color
|
104
|
+
output.print "\e[?25l" + ERROR_STATE_COLORS[@error_state] if colour?
|
105
|
+
yield
|
106
|
+
output.print "\e[0m\e[?25h" if colour?
|
107
|
+
end
|
108
|
+
|
109
|
+
def mark_error_state_failed
|
110
|
+
@error_state = :some_failed
|
111
|
+
end
|
112
|
+
|
113
|
+
def mark_error_state_pending
|
114
|
+
@error_state = :some_pending unless @error_state == :some_failed
|
115
|
+
end
|
116
|
+
|
117
|
+
def erase_current_line
|
118
|
+
output.print "\e[K"
|
119
|
+
end
|
120
|
+
|
121
|
+
def print_warning_if_slow(group, example, elapsed)
|
122
|
+
if elapsed > THRESHOLD
|
123
|
+
erase_current_line
|
124
|
+
output.print yellow("SLOW SPEC: #{sprintf("%.4f", elapsed)} ")
|
125
|
+
output.print " #{group} #{example}"
|
126
|
+
output.puts
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<kml xmlns="http://earth.google.com/kml/2.2">
|
3
|
+
<Document>
|
4
|
+
<name>Multi-Polygon</name>
|
5
|
+
<description><![CDATA[]]></description>
|
6
|
+
<Style id="style1">
|
7
|
+
<LineStyle>
|
8
|
+
<color>40000000</color>
|
9
|
+
<width>3</width>
|
10
|
+
</LineStyle>
|
11
|
+
<PolyStyle>
|
12
|
+
<color>73FF0000</color>
|
13
|
+
<fill>1</fill>
|
14
|
+
<outline>1</outline>
|
15
|
+
</PolyStyle>
|
16
|
+
</Style>
|
17
|
+
<Style id="style3">
|
18
|
+
<LineStyle>
|
19
|
+
<color>40000000</color>
|
20
|
+
<width>3</width>
|
21
|
+
</LineStyle>
|
22
|
+
<PolyStyle>
|
23
|
+
<color>73FF0000</color>
|
24
|
+
<fill>1</fill>
|
25
|
+
<outline>1</outline>
|
26
|
+
</PolyStyle>
|
27
|
+
</Style>
|
28
|
+
<Style id="style2">
|
29
|
+
<LineStyle>
|
30
|
+
<color>40000000</color>
|
31
|
+
<width>3</width>
|
32
|
+
</LineStyle>
|
33
|
+
<PolyStyle>
|
34
|
+
<color>73FF0000</color>
|
35
|
+
<fill>1</fill>
|
36
|
+
<outline>1</outline>
|
37
|
+
</PolyStyle>
|
38
|
+
</Style>
|
39
|
+
<Placemark>
|
40
|
+
<name>Colorado</name>
|
41
|
+
<description><![CDATA[]]></description>
|
42
|
+
<styleUrl>#style1</styleUrl>
|
43
|
+
<Polygon>
|
44
|
+
<outerBoundaryIs>
|
45
|
+
<LinearRing>
|
46
|
+
<tessellate>1</tessellate>
|
47
|
+
<coordinates>
|
48
|
+
-109.053040,41.002705,0.000000
|
49
|
+
-102.046509,41.006847,0.000000
|
50
|
+
-102.041016,36.991585,0.000000
|
51
|
+
-109.048920,36.997070,0.000000
|
52
|
+
-109.053040,41.002705,0.000000
|
53
|
+
</coordinates>
|
54
|
+
</LinearRing>
|
55
|
+
</outerBoundaryIs>
|
56
|
+
</Polygon>
|
57
|
+
</Placemark>
|
58
|
+
<Placemark>
|
59
|
+
<name>Paris</name>
|
60
|
+
<description><![CDATA[]]></description>
|
61
|
+
<styleUrl>#style3</styleUrl>
|
62
|
+
<Polygon>
|
63
|
+
<outerBoundaryIs>
|
64
|
+
<LinearRing>
|
65
|
+
<tessellate>1</tessellate>
|
66
|
+
<coordinates>
|
67
|
+
1.593018,49.109837,0.000000
|
68
|
+
1.571045,48.257599,0.000000
|
69
|
+
3.186035,48.275883,0.000000
|
70
|
+
3.186035,49.167339,0.000000
|
71
|
+
1.593018,49.109837,0.000000
|
72
|
+
</coordinates>
|
73
|
+
</LinearRing>
|
74
|
+
</outerBoundaryIs>
|
75
|
+
</Polygon>
|
76
|
+
</Placemark>
|
77
|
+
<Placemark>
|
78
|
+
<name>Australia</name>
|
79
|
+
<description><![CDATA[]]></description>
|
80
|
+
<styleUrl>#style2</styleUrl>
|
81
|
+
<Polygon>
|
82
|
+
<outerBoundaryIs>
|
83
|
+
<LinearRing>
|
84
|
+
<tessellate>1</tessellate>
|
85
|
+
<coordinates>
|
86
|
+
120.673828,-14.179186,0.000000
|
87
|
+
107.534180,-25.918526,0.000000
|
88
|
+
111.840820,-37.544579,0.000000
|
89
|
+
148.183594,-44.559162,0.000000
|
90
|
+
157.543945,-30.789038,0.000000
|
91
|
+
148.051758,-10.617418,0.000000
|
92
|
+
135.131836,-9.188870,0.000000
|
93
|
+
120.673828,-14.179186,0.000000
|
94
|
+
</coordinates>
|
95
|
+
</LinearRing>
|
96
|
+
</outerBoundaryIs>
|
97
|
+
</Polygon>
|
98
|
+
</Placemark>
|
99
|
+
</Document>
|
100
|
+
</kml>
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: border_patrol
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Zach Brock
|
14
|
+
- Matt Wilson
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-10-20 00:00:00 -07:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - "="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 49
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 8
|
32
|
+
- 7
|
33
|
+
version: 0.8.7
|
34
|
+
name: rake
|
35
|
+
prerelease: false
|
36
|
+
requirement: *id001
|
37
|
+
type: :development
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - "="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 27
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
version: 1.3.0
|
50
|
+
name: rspec
|
51
|
+
prerelease: false
|
52
|
+
requirement: *id002
|
53
|
+
type: :development
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
name: progressbar
|
65
|
+
prerelease: false
|
66
|
+
requirement: *id003
|
67
|
+
type: :development
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - "="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 113
|
75
|
+
segments:
|
76
|
+
- 1
|
77
|
+
- 4
|
78
|
+
- 3
|
79
|
+
- 1
|
80
|
+
version: 1.4.3.1
|
81
|
+
name: nokogiri
|
82
|
+
prerelease: false
|
83
|
+
requirement: *id004
|
84
|
+
type: :runtime
|
85
|
+
description: Lets you import a KML file and then check if points are inside or outside the polygons the file defines.
|
86
|
+
email: eng@squareup.com
|
87
|
+
executables: []
|
88
|
+
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files: []
|
92
|
+
|
93
|
+
files:
|
94
|
+
- lib/border_patrol/polygon.rb
|
95
|
+
- lib/border_patrol/region.rb
|
96
|
+
- lib/border_patrol.rb
|
97
|
+
- lib/VERSION
|
98
|
+
- spec/lib/border_patrol/polygon_spec.rb
|
99
|
+
- spec/lib/border_patrol/region_spec.rb
|
100
|
+
- spec/lib/border_patrol_spec.rb
|
101
|
+
- spec/spec.opts
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/support/colorado-test.kml
|
104
|
+
- spec/support/formatters/compact_progress_bar_formatter.rb
|
105
|
+
- spec/support/multi-polygon-test.kml
|
106
|
+
- border_patrol.gemspec
|
107
|
+
- Rakefile
|
108
|
+
- README.markdown
|
109
|
+
- .gitignore
|
110
|
+
- Gemfile
|
111
|
+
- Gemfile.lock
|
112
|
+
has_rdoc: true
|
113
|
+
homepage: http://github.com/square/border_patrol
|
114
|
+
licenses: []
|
115
|
+
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options:
|
118
|
+
- --charset=UTF-8
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
hash: 3
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
version: "0"
|
139
|
+
requirements: []
|
140
|
+
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 1.3.7
|
143
|
+
signing_key:
|
144
|
+
specification_version: 3
|
145
|
+
summary: Import and query KML regions
|
146
|
+
test_files:
|
147
|
+
- spec/lib/border_patrol/polygon_spec.rb
|
148
|
+
- spec/lib/border_patrol/region_spec.rb
|
149
|
+
- spec/lib/border_patrol_spec.rb
|
150
|
+
- spec/spec.opts
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/support/colorado-test.kml
|
153
|
+
- spec/support/formatters/compact_progress_bar_formatter.rb
|
154
|
+
- spec/support/multi-polygon-test.kml
|