planefinder 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +108 -0
- data/Rakefile +1 -0
- data/db/create_db.rb +42 -0
- data/db/geocoding.db +0 -0
- data/db/state_latlon.csv +56 -0
- data/db/zip_code_database.csv +42523 -0
- data/lib/planefinder.rb +67 -0
- data/lib/planefinder/airplane_category.rb +30 -0
- data/lib/planefinder/airplane_geocoder.rb +66 -0
- data/lib/planefinder/airplane_listing.rb +68 -0
- data/lib/planefinder/airplane_make.rb +24 -0
- data/lib/planefinder/airplane_model.rb +28 -0
- data/lib/planefinder/version.rb +3 -0
- data/planefinder.gemspec +30 -0
- data/spec/fixtures/airplane_categories.json +1 -0
- data/spec/fixtures/da40xl_airplanes.json +1 -0
- data/spec/fixtures/diamond_models.json +1 -0
- data/spec/fixtures/jet_makes.json +1 -0
- data/spec/fixtures/piston_helicopter_makes.json +1 -0
- data/spec/fixtures/robinson_models.json +1 -0
- data/spec/fixtures/single_engine_makes.json +1 -0
- data/spec/planefinder/airplane_category_spec.rb +34 -0
- data/spec/planefinder/airplane_geocoder_spec.rb +87 -0
- data/spec/planefinder/airplane_listing_spec.rb +217 -0
- data/spec/planefinder/airplane_make_spec.rb +32 -0
- data/spec/planefinder/airplane_model_spec.rb +54 -0
- data/spec/planefinder_spec.rb +106 -0
- data/spec/spec_helper.rb +57 -0
- metadata +202 -0
data/lib/planefinder.rb
ADDED
@@ -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
|
data/planefinder.gemspec
ADDED
@@ -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
|