planefinder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ require "httparty"
2
+ require "json"
3
+ require "uri"
4
+ require "geokit"
5
+ require_relative "./planefinder/version"
6
+ require_relative "./planefinder/airplane_category"
7
+ require_relative "./planefinder/airplane_make"
8
+ require_relative "./planefinder/airplane_model"
9
+ require_relative "./planefinder/airplane_listing"
10
+ require_relative "./planefinder/airplane_geocoder"
11
+
12
+ module Planefinder
13
+ include HTTParty
14
+ base_uri 'www.trade-a-plane.com'
15
+
16
+ @@urls = {}
17
+ @@urls[:airplane_categories] = "/app_ajax/get_categories?listing_type_id=1"
18
+ @@urls[:airplane_makes_for_category] = "/app_ajax/get_aircraft_makes?category_id=%s&make_type_id=1"
19
+ @@urls[:airplane_models_for_category_and_make] = "/app_ajax/get_aircraft_models?category_id=%s&make_id=%s"
20
+ @@urls[:search_by_model_make_category] = "/app_ajax/search?s-type=aircraft&model=%s&make=%s&category=%s"
21
+
22
+ def self.valid_args?(*args)
23
+ args.all? { |a| a.to_i > 0 }
24
+ end
25
+
26
+ def self.get_categories
27
+ response = self.get(@@urls[:airplane_categories])
28
+ categories = []
29
+ JSON.parse(response.body).each do |cat|
30
+ next if(cat.has_key?('special_use_counts'))
31
+ categories << AirplaneCategory.new(cat)
32
+ end
33
+ categories
34
+ end
35
+
36
+ def self.get_makes_for_category(category)
37
+ response = self.get(@@urls[:airplane_makes_for_category] % category.id)
38
+ makes = []
39
+ JSON.parse(response.body).each do |make|
40
+ makes << AirplaneMake.new(make, category)
41
+ end
42
+ makes
43
+ end
44
+
45
+ def self.get_models_for_category_and_make(category, make)
46
+ response = self.get(@@urls[:airplane_models_for_category_and_make] % [category.id, make.id])
47
+ models = []
48
+ JSON.parse(response.body).each do |model|
49
+ models << AirplaneModel.new(model, category, make)
50
+ end
51
+ models
52
+ end
53
+
54
+ def self.search_by_model_make_category(model, make, cat)
55
+ response = self.get(@@urls[:search_by_model_make_category] % [model, make, cat].map { |s| URI.escape(s.name) })
56
+ listings = []
57
+ JSON.parse(response.body).each do |listing|
58
+ listings << AirplaneListing.new(listing)
59
+ end
60
+ listings
61
+ end
62
+
63
+ class << Planefinder
64
+ alias_method :get_makes, :get_makes_for_category
65
+ alias_method :get_models, :get_models_for_category_and_make
66
+ end
67
+ end
@@ -0,0 +1,30 @@
1
+ module Planefinder
2
+ # Contains categories like "Single Engine Piston" or "Jet" or "Turbine Helicopter"
3
+ class AirplaneCategory
4
+ attr_reader :count, :id, :name, :sort_priority
5
+
6
+ def initialize(json_category)
7
+ validate_category(json_category)
8
+ @count = json_category['count'].to_i
9
+ @id = json_category['id'].to_i
10
+ @name = json_category['name'].to_s
11
+ @sort_priority = json_category['sort_priority'].to_i
12
+ end
13
+
14
+ def validate_category(cat)
15
+ throw "Category must have count, id, name, and sort_priority fields" unless
16
+ ['count', 'id', 'name', 'sort_priority'].all? { |field| cat.include?(field) }
17
+ end
18
+
19
+ def ==(other)
20
+ @count == other.count &&
21
+ @id == other.id &&
22
+ @name == other.name &&
23
+ @sort_priority == other.sort_priority
24
+ end
25
+
26
+ def get_makes
27
+ Planefinder.get_makes_for_category(self)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ require "geokit"
2
+ require "sequel"
3
+
4
+ module Geokit
5
+ module Geocoders
6
+ __define_accessors
7
+
8
+ # Replace name 'External' (below) with the name of your custom geocoder class
9
+ # and use :external to specify this geocoder in your list of geocoders.
10
+ class AirplaneGeocoder < Geocoder
11
+ private
12
+ def self.do_geocode(address, options = {})
13
+ record = case address
14
+ when /,/
15
+ city, state = address.split(',').map(&:strip)
16
+ self.lookup_by_city_state(city, state)
17
+ when /^\d{5}$/
18
+ self.lookup_by_zip(address)
19
+ when /[A-Z]{2}/
20
+ self.lookup_by_state(address)
21
+ else
22
+ self.lookup_by_phone(address) if self.us_phone_number?(address)
23
+ end
24
+ record ? LatLng.new(record[:latitude], record[:longitude]) : LatLng.new
25
+ end
26
+
27
+ def self.db
28
+ File.join(File.dirname(__FILE__), "../../db/geocoding.db")
29
+ end
30
+
31
+ def self.lookup_by_city_state(city, state)
32
+ Sequel.sqlite(self.db) do |db|
33
+ db[:zip_codes].first(:city => city, :state => state)
34
+ end
35
+ end
36
+
37
+ def self.lookup_by_state(state)
38
+ Sequel.sqlite(self.db) do |db|
39
+ db[:states].first(:abbreviation => state)
40
+ end
41
+ end
42
+
43
+ def self.lookup_by_zip(zip)
44
+ Sequel.sqlite(self.db) do |db|
45
+ db[:zip_codes].first(:zip => zip)
46
+ end
47
+ end
48
+
49
+ def self.lookup_by_phone(phone)
50
+ area_code = self.sanitize_phone(phone)[0, 3]
51
+ record = Sequel.sqlite(self.db) do |db|
52
+ db[:zip_codes].where(Sequel.like(:area_codes, area_code)).first
53
+ end
54
+ record ? self.lookup_by_state(record[:state]) : nil
55
+ end
56
+
57
+ def self.sanitize_phone(str)
58
+ str.gsub(/[+( ).-]/, '')[/\d{10}$/]
59
+ end
60
+
61
+ def self.us_phone_number?(str)
62
+ self.sanitize_phone(str) =~ /^\d{10}$/
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,68 @@
1
+ module Planefinder
2
+ # Contains an airplane listing, including contact info, total engine time, etc.
3
+ class AirplaneListing
4
+ attr_reader :properties
5
+
6
+ PREFERRED_PROPERTY_ORDER = ['zipcode', 'city', 'state', 'home_phone', 'work_phone', 'fax',
7
+ 'aff_zip', 'aff_city', 'aff_state', 'aff_home_phone', 'aff_work_phone', 'aff_fax']
8
+
9
+ def initialize(listing_hash)
10
+ # replace empty strings with nil
11
+ @properties = listing_hash.inject({}) do |h, (k,v)|
12
+ v == "" ? h[k] = nil : h[k] = v
13
+ h
14
+ end
15
+ define_attributes(@properties)
16
+ end
17
+
18
+ def metaclass
19
+ class << self
20
+ self
21
+ end
22
+ end
23
+
24
+ def define_attributes(hash)
25
+ hash.each do |k, v|
26
+ next if instance_variable_defined?("@#{k}") || self.class.method_defined?(k.to_sym)
27
+ metaclass.send :attr_reader, k
28
+ instance_variable_set("@#{k}".to_sym, v)
29
+ end
30
+ end
31
+
32
+ def location
33
+ Geokit::Geocoders::AirplaneGeocoder.geocode(location_text) if location_type
34
+ end
35
+
36
+ def location_type
37
+ PREFERRED_PROPERTY_ORDER.each do |p|
38
+ if p =~ /city/
39
+ city = p
40
+ state = p.gsub('city', 'state')
41
+ return "#{city}, #{state}" if @properties[city] and @properties[state]
42
+ else
43
+ return p if @properties[p]
44
+ end
45
+ end
46
+ nil
47
+ end
48
+
49
+ def location_text
50
+ # the type might be a "city, state", but otherwise treat it like a single value
51
+ types = location_type.split(", ")
52
+ types.length == 1 ? @properties[types[0]] : "#{@properties[types[0]]}, #{@properties[types[1]]}"
53
+ end
54
+
55
+ def location_description
56
+ location_type + ": " + location_text
57
+ end
58
+
59
+ def [](property_name)
60
+ property_name = property_name.to_s if property_name.class == Symbol
61
+ @properties[property_name]
62
+ end
63
+
64
+ def to_s
65
+ @properties.inspect
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,24 @@
1
+ module Planefinder
2
+ # Contains aircraft makes like "Cessna" or "Robinson"
3
+ class AirplaneMake
4
+ attr_reader :count, :id, :name, :category
5
+
6
+ def initialize(json, category)
7
+ @count = json['count'].to_i
8
+ @id = json['id'].to_i
9
+ @name = json['name'].to_s
10
+ @category = category
11
+ end
12
+
13
+ def ==(other)
14
+ @count == other.count &&
15
+ @id == other.id &&
16
+ @name == other.name &&
17
+ @category == other.category
18
+ end
19
+
20
+ def get_models
21
+ Planefinder.get_models_for_category_and_make(@category, self)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ module Planefinder
2
+ # Contains aircraft models like "DA20" (Diamond) or "172" (Cessna)
3
+ class AirplaneModel
4
+ attr_reader :count, :id, :model_group, :name, :category, :make
5
+
6
+ def initialize(json, category, make)
7
+ @count = json['count'].to_i
8
+ @id = json['id'].to_i
9
+ @model_group = json['model_group'].to_s
10
+ @name = json['name'].to_s
11
+ @category = category
12
+ @make = make
13
+ end
14
+
15
+ def ==(other)
16
+ @count == other.count &&
17
+ @id == other.id &&
18
+ @model_group == other.model_group &&
19
+ @name == other.name &&
20
+ @category == other.category &&
21
+ @make == other.make
22
+ end
23
+
24
+ def get_listings
25
+ Planefinder.search_by_model_make_category(self, @make, @category)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Planefinder
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'planefinder/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "planefinder"
8
+ spec.version = Planefinder::VERSION
9
+ spec.authors = ["Dave Ceddia"]
10
+ spec.email = ["dceddia@gmail.com"]
11
+ spec.description = %q{Find airplanes for sale and their approximate locations}
12
+ spec.summary = %q{Find airplanes for sale and narrow results by location}
13
+ spec.homepage = "https://github.com/dceddia/planefinder"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "httparty"
22
+ spec.add_dependency "geokit"
23
+ spec.add_dependency "sequel"
24
+ spec.add_dependency "sqlite3"
25
+
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "bundler", "~> 1.3"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "fakeweb"
30
+ end
@@ -0,0 +1 @@
1
+ [{"sort_priority":"1","count":"2429","name":"Single Engine Piston","id":"1"},{"sort_priority":"2","count":"599","name":"Multi Engine Piston","id":"2"},{"sort_priority":"3","count":"350","name":"TurboProp","id":"3"},{"sort_priority":"4","count":"370","name":"Jet","id":"4"},{"sort_priority":"5","count":"110","name":"Piston Helicopter","id":"100"},{"sort_priority":"6","count":"145","name":"Turbine Helicopter","id":"101"},{"sort_priority":"7","count":"6","name":"Ultralight","id":"6"},{"sort_priority":"8","count":"3","name":"Glider / Sailplane","id":"7"},{"sort_priority":"10","count":"3","name":"Rotary Wing","id":"9"},{"special_use_counts":{"rebuildable":"24","antique":"234","agricultural":"40","aerobatic":"77","light_sport":"106","amphibious":"170","warbird":"94","homebuilt":"129","utility":"50"}}]
@@ -0,0 +1 @@
1
+ [{"home_phone":"6514506200","overhaul_4_type":null,"serial_number":"40.766","aff_name":"Exclusive Aviation","company_state":"MN","overhaul_3_type":null,"suppress_url":"f","aff_country":"USA","aff_city":"St Paul","email":"sales@exclusiveaviation.com","url":"www.exclusiveaviation.com","category":"Single Engine Piston","overhaul_2_time":null,"flight_rules":"","aff_state":"MN","model_group":"DA40 Series","prop_4_time":null,"description":"1980 Diamond DA40XL, 870 TT A&E, G1000 Avionics Suite, Active Traffic, GFC700 autopilot, LR tanks, complete logs.","overhaul_2_type":null,"registration_number":"N866DS","prop_2_time":null,"seller_id":"66538","hot_section_2_time":null,"seller_category":"Dealer","aff_email":"gary@exclusiveaviation.com","aff_work_phone":"","total_seats":null,"overhaul_1_time":"870","add_date":"2012-07-20 08:19:03","aerobatic":"f","category_id":"1","fax":"6514506201","utility":"f","hot_section_1_time":null,"hot_section_3_time":null,"country":"USA","prop_1_time":null,"overhaul_1_type":"","update_date":"12/6/2012","warbird":"f","total_time":"870","year":"2007","model":"DA40XL","company_name":"Exclusive Aviation","price_extension":null,"state":"MN","suppress_address":"f","work_phone":"","rebuildable":"f","year_interior":null,"aff_fax":"","sale_status":"For Sale","end_date":"2012-07-20 00:00:00","price":"215000.00","listing_id":"1581835","agricultural":"f","prop_3_time":null,"aff_contact_name":"Gary Newhard - Piston Sales","rating":"1","aff_addr1":"607 Eaton St","listing_type_id":"1","city":"","light_sport":"f","company_country":"USA","show_serial":"t","aff_addr2":"","overhaul_3_time":null,"start_date":"2012-07-20 00:00:00","suppress_phone_2":"f","amphibious":"f","make":"Diamond","suppress_phone":"f","suppress_email":"f","zipcode":"","company_city":"St Paul","aff_home_phone":"9522507817","aff_zip":"55107","overhaul_4_time":null,"hot_section_4_time":null,"homebuilt":"f","suppress_name":"f","year_painted":null,"condition":"Used","modify_date":"2012-12-06 12:07:38"},{"home_phone":"619-227-7932","overhaul_4_type":null,"serial_number":"40.720","aff_name":null,"company_state":"CA","overhaul_3_type":null,"suppress_url":"f","aff_country":null,"aff_city":null,"email":"tlancemurray@mac.com","url":"","category":"Single Engine Piston","overhaul_2_time":null,"flight_rules":"IFR","aff_state":null,"model_group":"DA40 Series","prop_4_time":null,"description":"Beautiful 2007 Diamond DA40XL. NO DAMAGE HISTORY This is the model that included all the options including the GFC-700 autopilot and long range tanks. You will not find a better Diamond DA40XL for the price. I am not a broker/dealer. Principals only unless you have a buyer and no leaseback requests.","overhaul_2_type":null,"registration_number":"N720LM","prop_2_time":null,"seller_id":"71087","hot_section_2_time":null,"seller_category":"Transient","aff_email":null,"aff_work_phone":null,"total_seats":"4","overhaul_1_time":null,"add_date":"2013-05-10 01:16:50","aerobatic":"f","category_id":"1","fax":"","utility":"t","hot_section_1_time":null,"hot_section_3_time":null,"country":"USA","prop_1_time":null,"overhaul_1_type":null,"update_date":"5/28/2013","warbird":"f","total_time":"1650","year":"2007","model":"DA40XL","company_name":"Lance Murray Aviation LLC","price_extension":null,"state":"CA","suppress_address":"t","work_phone":"","rebuildable":"f","year_interior":"2012","aff_fax":null,"sale_status":"For Sale","end_date":"2013-06-10 00:00:00","price":"189500.00","listing_id":"1654691","agricultural":"f","prop_3_time":null,"aff_contact_name":null,"rating":"1","aff_addr1":null,"listing_type_id":"1","city":"San Diego","light_sport":"f","company_country":"USA","show_serial":"t","aff_addr2":null,"overhaul_3_time":null,"start_date":"2013-05-10 00:00:00","suppress_phone_2":"f","amphibious":"f","make":"Diamond","suppress_phone":"f","suppress_email":"f","zipcode":"","company_city":"San Marcos","aff_home_phone":null,"aff_zip":null,"overhaul_4_time":null,"hot_section_4_time":null,"homebuilt":"f","suppress_name":"t","year_painted":"2007","condition":"Used","modify_date":"2013-05-28 15:22:29"},{"home_phone":"8592339399","overhaul_4_type":"","serial_number":"","aff_name":null,"company_state":"KY","overhaul_3_type":"","suppress_url":"f","aff_country":null,"aff_city":null,"email":"sales@airmart.com","url":"http://www.airmart.com","category":"Single Engine Piston","overhaul_2_time":null,"flight_rules":"","aff_state":null,"model_group":"DA40 Series","prop_4_time":null,"description":"2007 DA40 XL, N893DS, 566 Snew on engine, airframe and prop, Integrated GFC700 AP, Garmin G100 PFD, P&I 8, NDH, Ann due 1/14.","overhaul_2_type":"","registration_number":"N893DS","prop_2_time":null,"seller_id":"48217","hot_section_2_time":null,"seller_category":"Dealer","aff_email":null,"aff_work_phone":null,"total_seats":null,"overhaul_1_time":"566","add_date":"2013-05-13 15:08:41","aerobatic":"f","category_id":"1","fax":"8592230222","utility":"f","hot_section_1_time":null,"hot_section_3_time":null,"country":"USA","prop_1_time":"566","overhaul_1_type":"SNEW","update_date":"5/24/2013","warbird":"f","total_time":"566","year":"2007","model":"DA40XL","company_name":"Airmart","price_extension":"Trades Considered","state":"KY","suppress_address":"f","work_phone":"8595339399","rebuildable":"f","year_interior":null,"aff_fax":null,"sale_status":"For Sale","end_date":"2013-07-19 00:00:00","price":"209000.00","listing_id":"1654806","agricultural":"f","prop_3_time":null,"aff_contact_name":null,"rating":"1","aff_addr1":null,"listing_type_id":"1","city":"","light_sport":"f","company_country":"USA","show_serial":"t","aff_addr2":null,"overhaul_3_time":null,"start_date":"2013-05-13 00:00:00","suppress_phone_2":"f","amphibious":"f","make":"Diamond","suppress_phone":"f","suppress_email":"f","zipcode":"","company_city":"Lexington","aff_home_phone":null,"aff_zip":null,"overhaul_4_time":null,"hot_section_4_time":null,"homebuilt":"f","suppress_name":"f","year_painted":null,"condition":"Used","modify_date":"2013-05-24 14:38:22"}]
@@ -0,0 +1 @@
1
+ [{"count":"1","model_group":"DA20 Series","name":"DA20-C1 Evolution","id":"4138"},{"count":"1","model_group":"DA20 Series","name":"DA20-C1 Katana","id":"4139"},{"count":"5","model_group":"DA40 Series","name":"DA40","id":"4140"},{"count":"2","model_group":"DA40 Series","name":"DA40 XLS","id":"4143"},{"count":"3","model_group":"DA40 Series","name":"DA40XL","id":"4146"},{"count":"1","model_group":"HK36 Series","name":"HK36 TTS","id":"4558"}]
@@ -0,0 +1 @@
1
+ [{"count":"8","name":"AERO","id":"5"},{"count":"13","name":"Airbus","id":"29"},{"count":"1","name":"Astra/Gulfstream","id":"50"},{"count":"11","name":"Beechcraft","id":"69"},{"count":"32","name":"Boeing","id":"77"},{"count":"1","name":"Bombardier","id":"81"},{"count":"10","name":"Bombardier/Challenger","id":"82"},{"count":"101","name":"Cessna","id":"114"},{"count":"25","name":"Dassault","id":"151"},{"count":"1","name":"Douglas","id":"158"},{"count":"4","name":"Eclipse","id":"167"},{"count":"8","name":"Embraer","id":"170"},{"count":"3","name":"Fairchild Dornier","id":"188"},{"count":"1","name":"Folland","id":"206"},{"count":"2","name":"Fouga","id":"208"},{"count":"14","name":"Gulfstream","id":"234"},{"count":"41","name":"Hawker","id":"242"},{"count":"47","name":"Learjet","id":"287"},{"count":"1","name":"Lockheed","id":"294"},{"count":"7","name":"McDonnell Douglas","id":"307"},{"count":"4","name":"Mikoyan","id":"317"},{"count":"5","name":"Mitsubishi","id":"323"},{"count":"1","name":"Morane-Saulnier","id":"329"},{"count":"1","name":"North American","id":"350"},{"count":"1","name":"PZL","id":"383"},{"count":"11","name":"Sabre","id":"420"},{"count":"1","name":"Soko","id":"448"},{"count":"11","name":"Westwind","id":"533"}]
@@ -0,0 +1 @@
1
+ [{"count":"4","name":"Bell","id":"70"},{"count":"10","name":"Hiller","id":"248"},{"count":"1","name":"Hughes","id":"255"},{"count":"79","name":"Robinson","id":"406"},{"count":"2","name":"Rotorway","id":"411"},{"count":"6","name":"Schweizer","id":"425"},{"count":"1","name":"Sikorsky","id":"437"},{"count":"1","name":"Vertical Aviation Technologies","id":"514"},{"count":"1","name":"Wanted All Makes","id":"742"}]
@@ -0,0 +1 @@
1
+ [{"count":"5","model_group":"R22 Series","name":"R22","id":"5153"},{"count":"7","model_group":"R22 Series","name":"R22 Beta","id":"5154"},{"count":"16","model_group":"R22 Series","name":"R22 Beta II","id":"5155"},{"count":"6","model_group":"R44 Series","name":"R44","id":"6175"},{"count":"6","model_group":"R44 Series","name":"R44 Astro","id":"5159"},{"count":"4","model_group":"R44 Series","name":"R44 Clipper","id":"5160"},{"count":"2","model_group":"R44 Series","name":"R44 Clipper II","id":"5161"},{"count":"1","model_group":"R44 Series","name":"R44 Newscopter","id":"5162"},{"count":"9","model_group":"R44 Series","name":"R44 Raven I","id":"5163"},{"count":"23","model_group":"R44 Series","name":"R44 Raven II","id":"5164"}]
@@ -0,0 +1 @@
1
+ [{"count":"4","name":"AMD","id":"37"},{"count":"1","name":"Aero Adventure","id":"6"},{"count":"6","name":"Aero Commander","id":"7"},{"count":"1","name":"Aerocar International","id":"665"},{"count":"1","name":"Aerocomp Inc.","id":"9"},{"count":"13","name":"Aeronca","id":"13"},{"count":"1","name":"Aeroprakt Ltd","id":"14"},{"count":"1","name":"Air Tractor","id":"28"},{"count":"3","name":"Alon Aircoupe","id":"568"},{"count":"1","name":"Alpi Aviation","id":"35"},{"count":"32","name":"American Champion","id":"38"},{"count":"1","name":"American Legend","id":"40"},{"count":"1","name":"Antonov","id":"44"},{"count":"4","name":"Arion Aircraft LLC","id":"673"},{"count":"30","name":"Aviat","id":"55"},{"count":"1","name":"Bakeng Duece","id":"64"},{"count":"1","name":"Barracuda","id":"668"},{"count":"1","name":"Bearhawk","id":"761"},{"count":"3","name":"Bede","id":"68"},{"count":"247","name":"Beechcraft","id":"69"},{"count":"1","name":"Bell","id":"70"},{"count":"29","name":"Bellanca","id":"72"},{"count":"11","name":"Boeing/Stearman","id":"78"},{"count":"1","name":"Bolkow","id":"80"},{"count":"1","name":"Burgess-Wright","id":"712"},{"count":"747","name":"Cessna","id":"114"},{"count":"1","name":"Chance Vought","id":"119"},{"count":"123","name":"Cirrus","id":"125"},{"count":"3","name":"Citabria","id":"625"},{"count":"10","name":"Columbia","id":"128"},{"count":"16","name":"Commander","id":"131"},{"count":"1","name":"Consolidated Vultee","id":"136"},{"count":"9","name":"Cubcrafters","id":"143"},{"count":"1","name":"Culver","id":"145"},{"count":"1","name":"Curtiss","id":"146"},{"count":"1","name":"Curtiss-Wright","id":"147"},{"count":"4","name":"Czech Sport Aircraft","id":"148"},{"count":"1","name":"Dakota Cub Aircraft","id":"780"},{"count":"1","name":"Davis Aircraft Corp","id":"152"},{"count":"24","name":"Dehavilland","id":"153"},{"count":"13","name":"Diamond","id":"155"},{"count":"3","name":"Dova","id":"574"},{"count":"1","name":"Dynaero","id":"161"},{"count":"1","name":"Eaa Biplane","id":"162"},{"count":"2","name":"Ekolot","id":"710"},{"count":"7","name":"Ercoupe","id":"174"},{"count":"1","name":"Ercoupe/Forney","id":"176"},{"count":"6","name":"Evektor","id":"180"},{"count":"2","name":"Extra Flugzeugbau","id":"185"},{"count":"2","name":"Fairchild","id":"187"},{"count":"1","name":"Fairey","id":"190"},{"count":"2","name":"Fantasy Air","id":"193"},{"count":"1","name":"Fisher","id":"197"},{"count":"10","name":"Flight Design","id":"571"},{"count":"3","name":"Focke Wulf","id":"204"},{"count":"1","name":"Fokker","id":"205"},{"count":"6","name":"Found Aircraft Co","id":"209"},{"count":"1","name":"Fuji","id":"214"},{"count":"1","name":"Funk","id":"215"},{"count":"3","name":"Gippsland Aeronautics / GippsAero","id":"221"},{"count":"12","name":"Glasair Aviation","id":"222"},{"count":"1","name":"Globe","id":"223"},{"count":"3","name":"Great Lakes","id":"226"},{"count":"1","name":"Great Plains","id":"227"},{"count":"6","name":"Grumman","id":"231"},{"count":"22","name":"Grumman/American General","id":"232"},{"count":"1","name":"Harmon","id":"237"},{"count":"3","name":"Helio","id":"245"},{"count":"5","name":"Homebuilt","id":"612"},{"count":"1","name":"Iar S.A. Brasov","id":"256"},{"count":"9","name":"Jabiru","id":"266"},{"count":"2","name":"Just Aircraft","id":"732"},{"count":"19","name":"Lake","id":"283"},{"count":"18","name":"Lancair","id":"284"},{"count":"2","name":"Laser","id":"286"},{"count":"1","name":"Liberty Aerospace","id":"293"},{"count":"2","name":"Loehle","id":"295"},{"count":"5","name":"Luscombe","id":"297"},{"count":"43","name":"Maule","id":"302"},{"count":"2","name":"Max Holste","id":"304"},{"count":"1","name":"Micco","id":"313"},{"count":"1","name":"Mitsubishi","id":"323"},{"count":"1","name":"Monocoupe","id":"327"},{"count":"129","name":"Mooney","id":"328"},{"count":"4","name":"Murphy","id":"333"},{"count":"3","name":"Mustang Aeronautics","id":"335"},{"count":"6","name":"Nanchang","id":"337"},{"count":"11","name":"Navion","id":"339"},{"count":"1","name":"Navy","id":"547"},{"count":"24","name":"North American","id":"350"},{"count":"2","name":"PZL","id":"383"},{"count":"1","name":"Papa 51 Ltd. Co.","id":"356"},{"count":"1","name":"Piel","id":"363"},{"count":"5","name":"Pilatus","id":"365"},{"count":"543","name":"Piper","id":"366"},{"count":"12","name":"Pitts","id":"369"},{"count":"3","name":"Porterfield","id":"371"},{"count":"1","name":"Preceptor","id":"373"},{"count":"2","name":"Produits Aviatech Inc","id":"377"},{"count":"1","name":"Quad City","id":"385"},{"count":"1","name":"Questair","id":"388"},{"count":"3","name":"RANS","id":"393"},{"count":"7","name":"REMOS","id":"575"},{"count":"3","name":"Republic","id":"401"},{"count":"1","name":"Rihn","id":"403"},{"count":"3","name":"Rockwell","id":"408"},{"count":"2","name":"Rutan","id":"414"},{"count":"1","name":"Ryan","id":"416"},{"count":"3","name":"Seawind","id":"430"},{"count":"1","name":"Sequoia","id":"432"},{"count":"1","name":"Sherpa","id":"600"},{"count":"3","name":"Siai Marchetti","id":"434"},{"count":"1","name":"Snow","id":"446"},{"count":"14","name":"Socata","id":"447"},{"count":"1","name":"Sonex","id":"450"},{"count":"1","name":"Speed Queen","id":"752"},{"count":"12","name":"Stinson","id":"468"},{"count":"7","name":"Storm Aircraft","id":"770"},{"count":"3","name":"Sukhoi","id":"473"},{"count":"4","name":"Swift","id":"476"},{"count":"2","name":"Symphony Aircraft","id":"477"},{"count":"4","name":"Taylorcraft","id":"480"},{"count":"3","name":"Tecnam","id":"484"},{"count":"1","name":"Thorp","id":"489"},{"count":"2","name":"Thunder Mustang","id":"763"},{"count":"1","name":"Thurston Aircraft Corporation","id":"491"},{"count":"1","name":"Tiger Aircraft","id":"493"},{"count":"2","name":"Travel Air","id":"496"},{"count":"33","name":"Vans","id":"510"},{"count":"1","name":"Varga","id":"511"},{"count":"6","name":"Velocity Aircraft","id":"513"},{"count":"12","name":"Waco","id":"579"},{"count":"2","name":"Wag Aero","id":"525"},{"count":"9","name":"Yakovlev","id":"542"},{"count":"6","name":"Zenair/Zenith","id":"543"},{"count":"1","name":"Zlin","id":"546"}]
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ module Planefinder
4
+ describe AirplaneCategory do
5
+ let(:json_categories) { JSON.parse(file_fixture('airplane_categories.json')) }
6
+
7
+ it "should be initializable with a JSON category description" do
8
+ ac = AirplaneCategory.new(json_categories.first)
9
+ ac.count.should == 2429
10
+ ac.id.should == 1
11
+ ac.name.should == "Single Engine Piston"
12
+ ac.sort_priority.should == 1
13
+ end
14
+
15
+ it "should throw an error with an invalid category" do
16
+ special = json_categories.select { |c| c.has_key?('special_use_counts') }
17
+ expect { AirplaneCategory.new(special) }.to raise_error
18
+ end
19
+
20
+ it "should support ==" do
21
+ a = AirplaneCategory.new(json_categories.first)
22
+ b = AirplaneCategory.new(json_categories.first)
23
+
24
+ a.should == b
25
+ end
26
+
27
+ it "should be able to fetch makes for its category" do
28
+ ac = AirplaneCategory.new(json_categories.first)
29
+ makes = [1, 2, 3]
30
+ Planefinder.should_receive(:get_makes_for_category).with(ac) { makes }
31
+ ac.get_makes.should == makes
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,87 @@
1
+ require "csv"
2
+
3
+ module Geokit
4
+ module Geocoders
5
+ describe AirplaneGeocoder do
6
+ it "should return a LatLng when given a valid city, state" do
7
+ loc = Geokit::Geocoders::AirplaneGeocoder.geocode('Boston, MA')
8
+ loc.class.should == LatLng
9
+ loc.should be_valid
10
+ end
11
+
12
+ it "should return invalid LatLng for invalid city, state combinations" do
13
+ Geokit::Geocoders::AirplaneGeocoder.geocode('New York, MA').should_not be_valid
14
+ Geokit::Geocoders::AirplaneGeocoder.geocode('Boston, CA').should_not be_valid
15
+ Geokit::Geocoders::AirplaneGeocoder.geocode('Total Junk').should_not be_valid
16
+ end
17
+
18
+ it "should return a LatLng when given a valid state" do
19
+ loc = Geokit::Geocoders::AirplaneGeocoder.geocode('MA')
20
+ loc.class.should == LatLng
21
+ loc.should be_valid
22
+ end
23
+
24
+ it "should return a LatLng for each state" do
25
+ CSV.foreach(File.join(File.dirname(__FILE__), "../../db/state_latlon.csv"), :headers => true) do |row|
26
+ state = row[0]
27
+ lat = row[1]
28
+ lng = row[2]
29
+ Geokit::Geocoders::AirplaneGeocoder.geocode(state).should == LatLng.new(lat, lng)
30
+ end
31
+ end
32
+
33
+ it "should return invalid LatLng for invalid state or long state name" do
34
+ Geokit::Geocoders::AirplaneGeocoder.geocode('New York').should_not be_valid
35
+ Geokit::Geocoders::AirplaneGeocoder.geocode('XX').should_not be_valid
36
+ end
37
+
38
+ it "should return a LatLng for a zip code" do
39
+ loc = Geokit::Geocoders::AirplaneGeocoder.geocode("90210")
40
+ loc.class.should == LatLng
41
+ loc.should be_valid
42
+ end
43
+
44
+ it "should return an invalid LatLng for invalid zipcode" do
45
+ Geokit::Geocoders::AirplaneGeocoder.geocode("99999").should_not be_valid
46
+ Geokit::Geocoders::AirplaneGeocoder.geocode("90210-1234").should_not be_valid
47
+ Geokit::Geocoders::AirplaneGeocoder.geocode("9021").should_not be_valid
48
+ end
49
+
50
+ it "should return a LatLng for a phone number" do
51
+ Geokit::Geocoders::AirplaneGeocoder.geocode("9785551212").should be_valid
52
+ end
53
+
54
+ it "should return an invalid LatLng for phone number with bad area code" do
55
+ Geokit::Geocoders::AirplaneGeocoder.geocode("9115551212").should_not be_valid
56
+ end
57
+
58
+ context "sanitize_phone" do
59
+ it "should sanitize phone numbers to be 10 digits" do
60
+ Geokit::Geocoders::AirplaneGeocoder.sanitize_phone('9785551212').should == '9785551212'
61
+ Geokit::Geocoders::AirplaneGeocoder.sanitize_phone('978-555-1212').should == '9785551212'
62
+ Geokit::Geocoders::AirplaneGeocoder.sanitize_phone('978.555.1212').should == '9785551212'
63
+ Geokit::Geocoders::AirplaneGeocoder.sanitize_phone('(978)555-1212').should == '9785551212'
64
+ Geokit::Geocoders::AirplaneGeocoder.sanitize_phone('(978) 555-1212').should == '9785551212'
65
+ Geokit::Geocoders::AirplaneGeocoder.sanitize_phone('+1 978-555-1212').should == '9785551212'
66
+ end
67
+ end
68
+
69
+ context "us_phone_number?" do
70
+ it "should return true if the argument looks like a phone number" do
71
+ Geokit::Geocoders::AirplaneGeocoder.us_phone_number?('9785551212').should be_true
72
+ Geokit::Geocoders::AirplaneGeocoder.us_phone_number?('978-555-1212').should be_true
73
+ Geokit::Geocoders::AirplaneGeocoder.us_phone_number?('978.555.1212').should be_true
74
+ Geokit::Geocoders::AirplaneGeocoder.us_phone_number?('(978)555-1212').should be_true
75
+ Geokit::Geocoders::AirplaneGeocoder.us_phone_number?('(978) 555-1212').should be_true
76
+ Geokit::Geocoders::AirplaneGeocoder.us_phone_number?('+1 978-555-1212').should be_true
77
+
78
+ end
79
+
80
+ it "should return false if the argument does not look like a phone number" do
81
+ Geokit::Geocoders::AirplaneGeocoder.us_phone_number?('978555121').should be_false
82
+ Geokit::Geocoders::AirplaneGeocoder.us_phone_number?('words').should be_false
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end