First public release
This commit is contained in:
parent
97b1290f6b
commit
70ba2ca5d8
3 changed files with 128 additions and 0 deletions
62
app.py
Normal file
62
app.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
from flask import Flask, render_template, send_from_directory
|
||||||
|
from flask_caching import Cache
|
||||||
|
import requests
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['CACHE_TYPE'] = 'SimpleCache'
|
||||||
|
app.config['CACHE_DEFAULT_TIMEOUT'] = 1800
|
||||||
|
cache = Cache(app)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return render_template("map.html")
|
||||||
|
|
||||||
|
@app.route("/gas.svg")
|
||||||
|
def favicon():
|
||||||
|
return send_from_directory(app.static_folder, "gas.svg")
|
||||||
|
|
||||||
|
@app.route("/gas.csv")
|
||||||
|
def gas():
|
||||||
|
data = cache.get("gas")
|
||||||
|
if data: return data
|
||||||
|
|
||||||
|
result = ["Sams https://www.samsclub.com/local/fuel-center/-/X"]
|
||||||
|
for s in samsdata():
|
||||||
|
if 'gasPrices' in s:
|
||||||
|
p = {11:0,16:0}
|
||||||
|
for grade in s['gasPrices']:
|
||||||
|
if grade['gradeId'] in p:
|
||||||
|
p[grade['gradeId']] = int(grade['price']*100)
|
||||||
|
result.append(f"{p[11]},{p[16]},{s['geoPoint']['latitude']},{s['geoPoint']['longitude']},{s['id']}")
|
||||||
|
result.append("Costco https://www.costco.com/warehouse-locations-X.html#:~:text=Gas%20Station")
|
||||||
|
for s in costcodata():
|
||||||
|
if 'US' == s['country'] and 'regular' in s['gasPrices'] and 'PR' != s['state']:
|
||||||
|
p = {'regular':0,'premium':0}
|
||||||
|
for grade in p:
|
||||||
|
p[grade] = int(float(s['gasPrices'][grade])*100)
|
||||||
|
result.append(f"{p['regular']},{p['premium']},{s['latitude']},{s['longitude']},{s['displayName']}")
|
||||||
|
data = "\n".join(result)
|
||||||
|
cache.set("gas", data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
@app.route("/sams.json")
|
||||||
|
def samsdata():
|
||||||
|
data = cache.get("sams")
|
||||||
|
if data: return data
|
||||||
|
|
||||||
|
url = 'https://www.samsclub.com/api/node/vivaldi/browse/v2/clubfinder/list?distance=10000&nbrOfStores=1000&singleLineAddr=10001'
|
||||||
|
response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0', 'Accept-Encoding': 'ztsd'})
|
||||||
|
data = response.json()
|
||||||
|
cache.set("sams", data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
@app.route("/costco.json")
|
||||||
|
def costcodata():
|
||||||
|
data = cache.get("costco")
|
||||||
|
if data: return data
|
||||||
|
|
||||||
|
url = 'https://www.costco.com/AjaxWarehouseBrowseLookupView?hasGas=true&populateWarehouseDetails=true'
|
||||||
|
response = requests.get(url, headers={'User-Agent': 'Mozilla', 'Accept-Encoding': 'gzip'})
|
||||||
|
data = response.json()[1:]
|
||||||
|
cache.set("costco", data)
|
||||||
|
return data
|
1
static/gas.svg
Normal file
1
static/gas.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFFFFF" d="M32 64C32 28.7 60.7 0 96 0L256 0c35.3 0 64 28.7 64 64l0 192 8 0c48.6 0 88 39.4 88 88l0 32c0 13.3 10.7 24 24 24s24-10.7 24-24l0-154c-27.6-7.1-48-32.2-48-62l0-64L384 64c-8.8-8.8-8.8-23.2 0-32s23.2-8.8 32 0l77.3 77.3c12 12 18.7 28.3 18.7 45.3l0 13.5 0 24 0 32 0 152c0 39.8-32.2 72-72 72s-72-32.2-72-72l0-32c0-22.1-17.9-40-40-40l-8 0 0 144c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 512c-17.7 0-32-14.3-32-32s14.3-32 32-32L32 64zM96 80l0 96c0 8.8 7.2 16 16 16l128 0c8.8 0 16-7.2 16-16l0-96c0-8.8-7.2-16-16-16L112 64c-8.8 0-16 7.2-16 16z"/></svg>
|
After Width: | Height: | Size: 624 B |
65
templates/map.html
Normal file
65
templates/map.html
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Gas Prices</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/gas.svg">
|
||||||
|
<base target="_blank">
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="anonymous">
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.leaflet-container a {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.Costco, .Sams {
|
||||||
|
display: flex;
|
||||||
|
border-radius: 33%;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.Costco {
|
||||||
|
background-color: rgba(227, 42, 54, 0.5);
|
||||||
|
}
|
||||||
|
.Sams {
|
||||||
|
background-color: rgba(0, 103, 160, 0.5);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map">Fetching the latest prices from Sam's and Costco's slow servers...</div>
|
||||||
|
<script>
|
||||||
|
fetch('gas.csv').then(response => response.ok ? response.text() : null).then(csv => {
|
||||||
|
if (!csv) {
|
||||||
|
document.getElementById("map").innerHTML="Query failed. You can try to reload, but if that doesn't work, sam's or costco probably blocked me.<br>If issues persist, feel free to email me: steven@stevenalexander.org";
|
||||||
|
return
|
||||||
|
}
|
||||||
|
document.getElementById("map").innerHTML=""
|
||||||
|
let map = L.map('map', {center: [38.25, -85.65], zoom: 11, layers: [L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png')]});
|
||||||
|
let storeLayers = {};
|
||||||
|
let store,url;
|
||||||
|
csv.split('\n').forEach(line => {
|
||||||
|
if (line.includes(',')) {
|
||||||
|
let [u, p, lat, lng, id] = line.split(',');
|
||||||
|
[u, p] = [u, p].map(cents => (cents/100).toFixed(2));
|
||||||
|
L.marker([lat,lng], {icon: L.divIcon({className: store, html: '<a href="'+url.replace("X",id)+'">$'+u+'\n$'+p+'</a>', iconSize: [44,44]})}).addTo(storeLayers[store])
|
||||||
|
} else {
|
||||||
|
[store,url] = line.split(" ");
|
||||||
|
storeLayers[store] = L.layerGroup().addTo(map)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let layerControl = L.control.layers(null, storeLayers, {collapsed: false}).addTo(map);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue