planefinder 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|