Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
vzctl committed Mar 25, 2016
0 parents commit ceda4d3
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.cache
*.html
8 changes: 8 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
source 'https://rubygems.org'

gem 'httparty'
gem 'haml'
gem 'google_maps_api-distance_matrix'
gem 'google_maps_api-directions'
gem 'activesupport'
gem 'chronic'
39 changes: 39 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (4.1.4)
i18n (~> 0.6, >= 0.6.9)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
chronic (0.10.2)
google_maps_api-core (0.2.0)
google_maps_api-directions (0.2.0)
google_maps_api-core (~> 0.2)
google_maps_api-distance_matrix (0.2.1)
google_maps_api-core (~> 0.2)
haml (4.0.6)
tilt
httparty (0.13.7)
json (~> 1.8)
multi_xml (>= 0.5.2)
i18n (0.7.0)
json (1.8.3)
minitest (5.5.0)
multi_xml (0.5.5)
thread_safe (0.3.4)
tilt (1.4.1)
tzinfo (1.2.2)
thread_safe (~> 0.1)

PLATFORMS
ruby

DEPENDENCIES
activesupport
chronic
google_maps_api-directions
google_maps_api-distance_matrix
haml
httparty
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Booli filter

This script filters out results from booli.se based on the distance to your interest point (work, city center, etc)
It uses Google API to measure the transit distance.

## Configuration

TODO

## API keys

TODO

45 changes: 45 additions & 0 deletions booli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'digest/sha1'
require 'httparty'

class Booli
include HTTParty
format :json
base_uri 'api.booli.se'
default_params format: 'json', limit: 150
# debug_output $stdout

def self.default_options
time = Time.new.strftime('%Y-%m-%dT%H:%M:%S%z')
unique = '%.16x' % rand(9**20)
auth = { callerId: @@config['appname'],
hash: Digest::SHA1.hexdigest(@@config['appname'] + time + @@config['appkey'] + unique),
time: time,
unique: unique }
@default_options[:default_params].update(auth)
@default_options
end

attr_accessor :result, :query, :what

def initialize(what, query, config)
@what, @query, @@config = what, query, config
fetch
end

def fetch(force=false)
if @fetch.nil? || force
query = @query.dup
response = self.class.get("/#{what}", query: query)
@fetch = response
end
@fetch
end

def each
fetch[what].each { |r| yield r }
end

def list
fetch[what]
end
end
29 changes: 29 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
departure_time: 'next Monday 10:20'
transit_options:
mode: 'transit'
key: 'CHANGEME'
https: true
# transit_mode: 'subway'
# transit_routing_preference: 'fewer_transfers'
walking_distance_limit: 800
transit_limits:
'T-Centralen, Stockholm': 25
'Birger Jarlsgatan 61, Stockholm': 40
minimal_floor: 3
maps_key: 'CHANGEME'
booli:
appname: 'CHANGEME'
appkey: 'CHANGEME'
query:
objectType: 'lägenhet'
maxListPrice: 2200000
minLivingArea: 49
maxRent: 5200
limit: 500
bboxes:
all:
- 59.236431
- 17.781703
- 59.431630
- 18.346754

44 changes: 44 additions & 0 deletions list.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
!!!
%html
%head
%meta{"http-equiv" => "Content-Type", :content => "text/html; charset=utf-8"}
%link{rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', integrity:'sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7', crossorigin:'anonymous'}
%link{rel: 'stylesheet', href: "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css", integrity:"sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r", crossorigin:"anonymous"}
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
%body
%table.table.table-striped
- results.each do |area, listings|
- listings.sort_by {|l| l['published']}.reverse.each do |listing|
%tr
%td
- if config[:bboxes].size > 1
= "#{area}:"
= "#{listing['location']['namedAreas'].first}"
%td.text-nowrap
%a{target: '_blank', href: "https://www.google.se/maps/place/#{listing['coordinates'].join(',')}"}
= "#{listing['address']}, #{listing['floor']}tr"
%td.text-nowrap= "#{listing['rooms']}, #{listing['livingArea']}m, #{listing['constructionYear']} year"
%td.text-nowrap= listing['listPrice']
%td.text-nowrap= listing['rent']
%td.text-nowrap
%strong
%a{target: '_blank', href: listing['url']}
= listing['published']
%td
- listing['routes'].each do |dst, route|
%b= dst
= route
%br
- listing['sold'].sort_by {|l| l['soldDate']}.reverse.each do |sold_listing|
- sold_address = sold_listing['location']['address']['streetAddress']
%tr
%td
%td
%a{target: '_blank', href: "https://www.google.se/maps/place/#{sold_address.gsub(' ', '+')},+Stockholm"}
= "#{sold_address}, #{sold_listing['floor']}tr"
%td.text-nowrap= "#{sold_listing['rooms']}, #{sold_listing['livingArea']}m, #{sold_listing['constructionYear']} year"
%td.text-nowrap= "#{(sold_listing['soldPrice'] * 1.0 / sold_listing['listPrice']).round(2) rescue 'error'}: #{sold_listing['soldPrice']}"
%td= sold_listing['rent']
%td
%a{target: '_blank', href: sold_listing['url']}
= sold_listing['soldDate']
55 changes: 55 additions & 0 deletions map.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Simple Polygon</title>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>

// This example creates a simple polygon representing the Bermuda Triangle.

function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 12,
center: {lat: 59.295378, lng: 17.991845},
});

// Define the LatLng coordinates for the polygon's path.
<% polygons.each do |polygon| %>
var triangleCoords = [
<% polygon.each do |coord| %>
{lat: <%= coord[0] %>, lng: <%= coord[1] %>},
<% end %>
];

// Construct the polygon.
var bermudaTriangle = new google.maps.Polygon({
paths: triangleCoords,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35
});
bermudaTriangle.setMap(map);
<% end %>
}

</script>
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=<%= config[:maps_key] %>&signed_in=true&callback=initMap"></script>
</body>
</html>
79 changes: 79 additions & 0 deletions search.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env ruby

require './booli'
require 'active_support/all'
require 'google_maps_api/directions'
require 'haml'
require 'erb'
require 'chronic'

def config
@file ||= YAML.load_file('config.yaml').symbolize_keys
end

def transit_options
return @transit_options if @transit_options
@transit_options = config[:transit_options].symbolize_keys.merge(departure_time: Chronic.parse(config[:departure_time]))
end

def cache_fetch(name, key)
ActiveSupport::Cache.lookup_store(:file_store, "./.cache/#{name}").fetch(key) { yield }
end

def route(origin, destination)
leg = cache_fetch('route', [transit_options] + origin + [destination]) do
directions = GoogleMapsAPI::Directions.route([origin], [destination], transit_options)
directions.routes.first.legs.first rescue []
end
summary = leg.steps[0..-2].map { |s| [s.distance, s.duration, s.html_instructions].join(', ') }.join(' | ')
["#{leg.duration.text}: #{summary}", leg]
end

query = config[:booli]['query']
query_sold = query.merge(minLivingArea: query['minLivingArea'] - 10,
minSoldDate: 6.months.ago.monday.strftime('%Y%m%d'),
dim: '200,200')

if config[:maps_key]
polygons = []
config[:bboxes].each do |name, coords|
polygon = []
polygon << [coords[0], coords[1]]
polygon << [coords[0], coords[3]]
polygon << [coords[2], coords[3]]
polygon << [coords[2], coords[1]]
polygon << [coords[0], coords[1]]
polygons << polygon
end
File.write('map.html', ERB.new(File.read('map.html.erb')).result(binding))
end

results = Hash.new { |h, k| h[k] = [] }

config[:bboxes].each do |name, bbox|
params = query.dup
params[:bbox] = bbox.join(',')
booli = Booli.new('listings', params, config[:booli])
fail 'increase limit' if booli.fetch['count'] != booli.fetch['totalCount']
booli.each do |a|
next if a['floor'].to_i < config[:minimal_floor]
a['coordinates'] = [a['location']['position']['latitude'], a['location']['position']['longitude']]
a['address'] = a['location']['address']['streetAddress']
a['routes'], skip = {}, false
config[:transit_limits].each do |dst, mins|
next if skip
a['routes'][dst], leg = route(a['coordinates'], dst)
skip = true if leg.duration.value > mins * 60
skip = true if leg.steps.first.distance.value > config[:walking_distance_limit]
end
next if skip
params_sold = query_sold.dup
params_sold[:maxLivingArea] = a['livingArea'] + 10
params_sold[:minLivingArea] = a['livingArea']
params_sold[:center] = a['coordinates'].join(',')
a['sold'] = cache_fetch('sold', params_sold) { Booli.new('sold', params_sold, config[:booli]).list }
results[name] << a
end
end

File.write('list.html', Haml::Engine.new(File.read('list.html.haml')).render(binding))

0 comments on commit ceda4d3

Please sign in to comment.