diff --git a/.rspec b/.rspec index a5faa1d..aded9b6 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1 @@ ---color ---backtrace +--color --tty --format nested diff --git a/Rakefile b/Rakefile index 20e5c87..57d00aa 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,6 @@ +require 'bundler/setup' +require 'bundler/gem_tasks' +require 'rake' require 'rspec/core/rake_task' require 'rubocop/rake_task' diff --git a/lib/border_patrol.rb b/lib/border_patrol.rb index 3263bb5..f0887ce 100644 --- a/lib/border_patrol.rb +++ b/lib/border_patrol.rb @@ -1,31 +1,64 @@ +require 'set' +require 'forwardable' +require 'nokogiri' +require 'border_patrol/version' +require 'border_patrol/point' +require 'border_patrol/polygon' +require 'border_patrol/region' + module BorderPatrol class InsufficientPointsToActuallyFormAPolygonError < ArgumentError; end - class Point < Struct.new(:x, :y); end def self.parse_kml(string) doc = Nokogiri::XML(string) polygons = doc.search('Polygon').map do |polygon_kml| - parse_kml_polygon_data(polygon_kml.to_s) + placemark_name = placemark_name_for_polygon(polygon_kml) + parse_kml_polygon_data(polygon_kml.to_s,placemark_name) end BorderPatrol::Region.new(polygons) end + def self.bounding_box(points) + max_x, min_x, max_y, min_y = -Float::MAX, Float::MAX, -Float::MAX, Float::MAX + points.each do |point| + max_y = point.y if point.y > max_y + min_y = point.y if point.y < min_y + max_x = point.x if point.x > max_x + min_x = point.x if point.x < min_x + end + [Point.new(min_x, max_y), Point.new(max_x, min_y)] + end + + def self.central_point(box) + point1, point2 = box + + x = (point1.x + point2.x) / 2 + y = (point1.y + point2.y) / 2 + + Point.new(x, y) + end + private - def self.parse_kml_polygon_data(string) + + def self.parse_kml_polygon_data(string, name = nil) doc = Nokogiri::XML(string) coordinates = doc.xpath('//coordinates').text.strip.split(/\s+/) points = coordinates.map do |coord| x, y, _ = coord.strip.split(',') BorderPatrol::Point.new(x.to_f, y.to_f) end - BorderPatrol::Polygon.new(points) + BorderPatrol::Polygon.new(points).with_placemark_name(name) + end + + def self.placemark_name_for_polygon(p) + # A polygon can be contained by a MultiGeometry or Placemark + parent = p.parent + parent = parent.parent if parent.name == "MultiGeometry" + + return nil unless parent.name == "Placemark" + + parent.search("name").text end end - -require 'set' -require 'nokogiri' -require 'border_patrol/version' -require 'border_patrol/polygon' -require 'border_patrol/region' diff --git a/lib/border_patrol/point.rb b/lib/border_patrol/point.rb new file mode 100644 index 0000000..b0e3d6a --- /dev/null +++ b/lib/border_patrol/point.rb @@ -0,0 +1,33 @@ +module BorderPatrol + Point = Struct.new(:x, :y) unless defined?(::BorderPatrol::Point) + + class Point + alias :latitude :y + alias :latitude= :y= + alias :lat :y + alias :lat= :y= + + alias :longitude :x + alias :longitude= :x= + alias :lng :x + alias :lng= :x= + alias :lon :x + alias :lon= :x= + + # Lots of Map APIs want the coordinates in lat-lng order + def latlng + [lat, lon] + end + alias :coords :latlng + + def inspect + self.class.inspect_string % self.latlng + end + + protected + # IE: # + def self.inspect_string + @inspect_string ||= "#<#{self.name}(lat, lng) = (%p, %p)>" + end + end +end diff --git a/lib/border_patrol/polygon.rb b/lib/border_patrol/polygon.rb index 6d5695c..00ffddb 100644 --- a/lib/border_patrol/polygon.rb +++ b/lib/border_patrol/polygon.rb @@ -1,6 +1,6 @@ -require 'forwardable' module BorderPatrol class Polygon + attr_reader :placemark_name extend Forwardable def initialize(*args) args.flatten! @@ -10,6 +10,11 @@ def initialize(*args) end def_delegators :@points, :size, :each, :first, :include?, :[], :index + + def with_placemark_name(placemark) + @placemark_name ||= placemark + self + end def ==(other) # Do we have the right number of points? @@ -63,14 +68,11 @@ def inside_bounding_box?(point) end def bounding_box - max_x, min_x, max_y, min_y = -Float::MAX, Float::MAX, -Float::MAX, Float::MAX - each do |point| - max_y = point.y if point.y > max_y - min_y = point.y if point.y < min_y - max_x = point.x if point.x > max_x - min_x = point.x if point.x < min_x - end - [Point.new(min_x, max_y), Point.new(max_x, min_y)] + BorderPatrol.bounding_box(self) + end + + def central_point + BorderPatrol.central_point(bounding_box) end end end diff --git a/lib/border_patrol/region.rb b/lib/border_patrol/region.rb index fa475d3..52e8d06 100644 --- a/lib/border_patrol/region.rb +++ b/lib/border_patrol/region.rb @@ -11,5 +11,21 @@ def contains_point?(*point) end any? { |polygon| polygon.contains_point?(point) } end + + # The below are some general helper methods + def bounding_boxes + map(&:bounding_box) + end + + def bounding_box + boxes = bounding_boxes + boxes.flatten! + + BorderPatrol.bounding_box(boxes) + end + + def central_point + BorderPatrol.central_point(bounding_box) + end end end diff --git a/spec/lib/border_patrol/polygon_spec.rb b/spec/lib/border_patrol/polygon_spec.rb index 98c5d33..d49431c 100644 --- a/spec/lib/border_patrol/polygon_spec.rb +++ b/spec/lib/border_patrol/polygon_spec.rb @@ -135,4 +135,32 @@ end end + + describe "#with_placemark_name" do + before(:each) do + points = [BorderPatrol::Point.new(-10, 0), BorderPatrol::Point.new(10, 0), BorderPatrol::Point.new(0, 10)] + @polygon = BorderPatrol::Polygon.new(points) + end + + it "adds a placemark name to a polygon" do + @polygon.placemark_name.should be_nil + + @polygon.with_placemark_name("Twin Peaks, San Francisco") + @polygon.placemark_name.should == "Twin Peaks, San Francisco" + end + + it "returns the Polygon object" do + @polygon.with_placemark_name("Silverlake, Los Angeles").should equal @polygon + end + + it "only allows the placemark name to be set once" do + @polygon.placemark_name.should be_nil + + @polygon.with_placemark_name("Santa Clara, California") + @polygon.placemark_name.should == "Santa Clara, California" + + @polygon.with_placemark_name("Santa Cruz, California") + @polygon.placemark_name.should == "Santa Clara, California" + end + end end diff --git a/spec/lib/border_patrol_spec.rb b/spec/lib/border_patrol_spec.rb index c64b6e5..56d3426 100644 --- a/spec/lib/border_patrol_spec.rb +++ b/spec/lib/border_patrol_spec.rb @@ -51,6 +51,84 @@ polygon.should eq(BorderPatrol::Polygon.new(BorderPatrol::Point.new(-10, 25), BorderPatrol::Point.new(-1, 30), BorderPatrol::Point.new(10, 1), BorderPatrol::Point.new(0, -5))) end end + + describe '.placemark_name_for_polygon' do + it 'returns the name of the placemark when Placemark is the parent node' do + kml_data = File.read(Support_Folder + "colorado-test.kml") + doc = Nokogiri::XML(kml_data) + polygon_node = doc.search('Polygon').first + + placemark_name = BorderPatrol.placemark_name_for_polygon(polygon_node) + placemark_name.should == "Shape 1" + end + + it 'returns the name of the placemark when MultiGeometry is the parent node' do + kml_data = File.read(Support_Folder + "elgin-opengis-ns-test.kml") + doc = Nokogiri::XML(kml_data) + polygon_node = doc.search('Polygon').first + + placemark_name = BorderPatrol.placemark_name_for_polygon(polygon_node) + placemark_name.should == "Elgin" + end + + it 'returns nil when there is no Placemark' do + kml = <<-EOM + + + #style1 + + + + 1 + + -109.053040,41.002705,0.000000 + -102.046509,41.006847,0.000000 + -102.041016,36.991585,0.000000 + -109.048920,36.997070,0.000000 + -109.053040,41.002705,0.000000 + + + + + + EOM + + doc = Nokogiri::XML(kml) + polygon_node = doc.search('Polygon').first + + placemark_name = BorderPatrol.placemark_name_for_polygon(polygon_node) + placemark_name.should be_nil + end + + it 'returns a blank string when there is no Placemark name' do + kml = <<-EOM + + + #style1 + + + + 1 + + -109.053040,41.002705,0.000000 + -102.046509,41.006847,0.000000 + -102.041016,36.991585,0.000000 + -109.048920,36.997070,0.000000 + -109.053040,41.002705,0.000000 + + + + + + EOM + + doc = Nokogiri::XML(kml) + polygon_node = doc.search('Polygon').first + + placemark_name = BorderPatrol.placemark_name_for_polygon(polygon_node) + placemark_name.should == "" + end + end describe BorderPatrol::Point do describe '==' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 16072d2..c264d0a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,5 @@ -require 'rubygems' -require 'bundler' -Bundler.setup +require 'bundler/setup' + +Bundler.require :development, :test, :default require 'border_patrol'