Move chains to their own directory

This commit is contained in:
Chandler Swift 2023-07-25 18:13:39 -05:00
parent f3f7a21645
commit c74edcb8c0
Signed by: chandlerswift
GPG key ID: A851D929D52FB93F
21 changed files with 50 additions and 40 deletions

View file

@ -0,0 +1,70 @@
#!/usr/bin/env python3
import requests
import json
print("Searching for Culver's locations")
response = requests.get('https://hosted.where2getit.com/lite?action=getstategeojsonmap&appkey=EC84928C-9C0B-11E4-B999-D8F4B845EC6E').json()
stores = []
state_names = {}
for f in response['states']['features']:
state_names[f['properties']['name']] = f['properties']['regiondesc']
for s in response['labels']['features']:
if s['properties']['num_stores'] > 0:
state = s['properties']['name']
print(f"Searching {state}...", end="")
json_data = {
'request': {
'appkey': '1099682E-D719-11E6-A0C4-347BDEB8F1E5',
'formdata': {
'geolocs': {
'geoloc': [
{
'addressline': state,
'state': state_names[state],
},
],
},
'stateonly': 1,
},
},
}
response = requests.post('https://hosted.where2getit.com/culvers/rest/locatorsearch', json=json_data)
count = 0
for store in response.json()['response']['collection']:
# I tried a bunch of other things and this is the only one that matched :facepalm:
if "coming soon" in store['name'].lower(): # store['comingsoondate']: # not store['dine_in'] and not store['takeout']: # not store['opendate']: # store['comingsoondate']:
# print(f"{store['name']} not yet open")
continue
stores.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [float(store['longitude']), float(store['latitude'])], # yes, [lon, lat] since it's [x, y]
},
"properties": {
'address': store['address1'],
'city': store['city'],
'state': store['state'],
'zip': store['postalcode'],
'website': store['url'],
},
})
count += 1
print(count)
if not count == s['properties']['num_stores']:
print(f"Inequal for {state}: {count} != {s['properties']['num_stores']}")
print(f"""{len(stores)} locations found""")
geojson = {
"type": "FeatureCollection",
"features": stores,
}
with open("culvers-data.geojson", "w") as f:
f.write(json.dumps(geojson))

View file

@ -0,0 +1,24 @@
import VectorLayer from 'ol/layer/Vector';
import {Vector as VectorSource} from 'ol/source.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import {Style} from 'ol/style.js';
import Icon from 'ol/style/Icon.js';
import culversURL from '/data/culvers-data.geojson?url'; // TODO: remove `?url`?
import pinURL from './pin.svg?url'; // TODO: remove `?url`?
const vectorLayer = new VectorLayer({
source: new VectorSource({
url: culversURL,
format: new GeoJSON,
}),
style: new Style({
image: new Icon({
anchor: [0.5, 1],
src: pinURL,
}),
}),
});
export default vectorLayer;

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 384 512"
width="40px"
height="30px"
version="1.1"
id="svg873"
sodipodi:docname="culvers-location-pin-solid.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs877" />
<sodipodi:namedview
id="namedview875"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.5917969"
inkscape:cx="180.6135"
inkscape:cy="296.20613"
inkscape:window-width="1916"
inkscape:window-height="1030"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g1175" />
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 0,279.4 0,192 0,85.96 85.96,0 192,0 245.96029,0 294.73775,22.275796 329.62577,58.136607 363.27205,92.721042 384,139.94065 384,192 Z"
id="path871"
sodipodi:nodetypes="sccssss"
style="fill:#005696;fill-opacity:1" />
<g
id="g1175"
transform="matrix(0.7011677,0,0,0.7011677,25.41615,116.65117)"
style="stroke-width:1.00260199;stroke-miterlimit:4;stroke-dasharray:none">
<g
id="g1167"
style="stroke-width:1.0026;stroke-miterlimit:4;stroke-dasharray:none">
<path
fill="#ffffff"
d="m 413.7,58.1 c 17.8,-0.7 26,-21.5 26,-32.2 0,-5.9 -2.7,-13.4 -8.7,-13.4 -7.3,0 -18.2,11.4 -18.2,24.4 0,8.7 4,9.7 3.9,11.1 -0.3,5.3 -11.5,10.5 -3,10.1 z m 57.5,16.5 c 0,13.7 -7.8,24.4 -16.5,25.1 5.5,-7.6 7,-10.3 8.4,-19.9 2.9,-22.5 -3,-22.4 -5.7,-34.8 0,0 3.2,-12.1 -2.4,-11 -23.8,4.4 -7.1,11.8 -40.8,64.5 -5.8,9.3 -13.7,12 -14.3,11.9 -11.8,-2 10.2,-21.7 11.1,-28.3 1.8,-15.1 -2.9,-23.2 -6.6,-22.9 -8.6,0.8 -15.9,6.2 -22.7,5.8 0,0 4.1,-6.2 -3.1,-6.2 -14.3,0 -22.5,25.8 -22.5,32.7 0,3.5 2.7,6.4 2.7,7.3 0,4.1 -13.6,27.4 -24,27.4 -6.1,0 -7,-7 -7,-8.2 0,-4.6 27.8,-17 27.8,-39.7 0,-5.8 -2.5,-13.4 -10.2,-13.4 -14.6,0 -29.1,14.3 -33.8,23.7 -13.3,25.6 0.8,29.1 -14.4,34.4 0,0 -7,2 -4.5,-5.7 0.9,-4.6 4.2,-8.5 5.6,-17.7 2.2,-15.4 -3.6,-15 -9.7,-14.6 -13.7,1 -17.1,27.5 -17.1,35 0,3.6 0,5.8 0,9.5 0,2.8 -6.2,15.5 -11.8,15.7 -2.6,0.1 -3.4,-9.4 -3.4,-11.6 0,-6 13.9,-18.7 13.9,-37.2 0,-5.9 -7.2,-6.6 -11.9,-6.6 -16.3,0 -18.8,43 -18.8,43 0,18 -15.9,21.8 -16.1,21.8 -7.7,0 -8.9,-19 -8.9,-19 -0.4,-9.8 25.8,-21.6 25.8,-74 0,-5.5 -2.4,-15.8 -8.2,-14.8 -18.7,3.3 -36.3,68.3 -34.7,90.8 -1.6,4.2 -15.8,23.3 -21.2,23.3 -2.6,0 -2.5,-2.2 -2.5,-5.3 0,-12.8 15.9,-29.9 15.9,-37.2 0,-7.3 -9.6,-18.2 -10.2,-18.2 -9.1,0 -22.4,30.4 -24.8,40.6 -2,8.6 -14.3,26.6 -18.8,24.9 -8.1,-3 14.3,-29.6 17.6,-37.2 2.9,-6.6 1.9,-11.2 -6.2,-12.6 -26.2,-4.5 -28.2,33 -30.6,40.4 -0.3,0.9 -35.1,45.8 -50.7,45.8 -8.1,0 -20.7,-1.9 -19.6,-30.2 C 48,129 90,76.5 99.8,74.9 c 6,-1.1 -1.5,30.7 -12.6,38 -18.1,11.9 -39.7,37.7 -22.9,47.8 25.5,15.5 44.1,-35.5 44.1,-35.5 30.7,-61.3 8.9,-75 -2.9,-75 -27.2,0 -93.7,80.3 -93.7,130.2 0,31 20.5,50.8 38.5,50.8 35.6,0 57.2,-42.2 67.7,-51.9 2.9,3 9.6,12.6 19.7,12.6 11.8,0 11.9,-16.3 18.1,-20.5 1.5,4.2 4.2,15 14.2,15 16.1,0 17.7,-19 27.5,-24.3 2.3,12.1 9.9,17.3 18.9,17.3 13.5,0 21.1,-12.2 27.2,-23 5.1,4 6.1,9 13.7,9.6 16.4,1.4 25.2,-26.4 26,-26.7 0,0 5.4,2.3 7.1,2.3 9.1,0 16.2,-9.7 17.5,-8.5 0.4,0.4 6.7,11.3 13.9,13 30.2,7.2 49,-36.4 53.6,-49.8 0.2,-0.6 7.1,-3.5 10.6,-4.9 -2.4,7 -10.2,19.3 -7.3,33.5 1.6,8.2 7.3,13.9 17.2,12.7 13.8,-1.6 22.3,-24.4 25.3,-25 6.6,-1.3 10,11 20.5,11 14.3,0 38.5,-19.1 38.5,-35.6 -0.3,-2.1 -2.4,-17.2 -9,-13.4 z m -258.8,36.3 -0.1,-0.2 v -0.1 c 0,-5.9 9,-27.7 12.3,-32.2 1,-1.3 2.1,-0.8 2.6,-0.5 0.6,0.5 2.1,1.6 -1.9,13.2 -0.1,0.2 -7.2,21 -11.3,21 -0.4,0.1 -1.2,0 -1.6,-1.2 z M 324.9,99.4 c -1.1,-3.5 6.8,-15.3 11.4,-16.7 2.1,-0.6 3.5,0.3 4,1.1 1.4,2.4 -1.3,7.4 -4.5,11 -7.4,7.2 -10.7,5.3 -10.9,4.6 z m 115.2,-3.6 c -1.5,0 -4.5,-1.6 -6.7,-3.1 8.2,-16.4 8,-19.7 11,-22 3.9,2.6 4.7,3.5 4.7,9.6 0.1,7.1 -4.1,15.5 -9,15.5 z"
id="path1165"
style="stroke-width:1.0026;stroke-miterlimit:4;stroke-dasharray:none" />
</g>
<g
id="g1171"
style="stroke-width:1.0026;stroke-miterlimit:4;stroke-dasharray:none">
<path
fill="#ffffff"
d="m 486.8,113.8 c -4.3,0 -7.6,-3.5 -7.6,-7.6 0,-4.2 3.4,-7.7 7.7,-7.7 4.3,0 7.6,3.5 7.6,7.6 0.1,4.2 -3.3,7.7 -7.7,7.7 z m 0.1,-14.5 c -3.9,0 -6.8,3.1 -6.8,6.9 0,3.8 2.9,6.8 6.8,6.8 3.9,0 6.8,-3.1 6.8,-6.9 0,-3.7 -2.9,-6.8 -6.8,-6.8 z m 1.9,7.8 2,2.9 h -2.1 l -1.7,-2.6 h -1.4 v 2.6 h -1.8 v -8.1 h 3.6 c 1.9,0 3.2,1 3.2,2.7 0,1.3 -0.8,2.2 -1.8,2.5 z m -1.5,-3.6 h -1.7 v 2.4 h 1.8 c 0.9,0 1.4,-0.5 1.4,-1.2 0,-0.8 -0.6,-1.2 -1.5,-1.2 z"
id="path1169"
style="stroke-width:1.0026;stroke-miterlimit:4;stroke-dasharray:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

38
layers/chains/index.js Normal file
View file

@ -0,0 +1,38 @@
import culversLayer from './culvers/layer.js';
import krispyKremeLayer from './krispy-kreme/layer.js';
import kwikTripLayer from './kwik-trip/layer.js';
import menardsLayer from './menards/layer.js';
import milwaukeeBurgerCompanyLayer from './milwaukee-burger-company/layer.js';
import waffleHouseLayer from './waffle-house/layer.js';
const chains = {
name: "Chains",
layers: [
{
name: "Culver's",
layer: culversLayer,
},
{
name: "Krispy Kreme",
layer: krispyKremeLayer,
},
{
name: "Kwik Trip/Kwik Star",
layer: kwikTripLayer,
},
{
name: "Menards",
layer: menardsLayer,
},
{
name: "Milwaukee Burger Company",
layer: milwaukeeBurgerCompanyLayer,
},
{
name: "Waffle House",
layer: waffleHouseLayer,
},
],
};
export default chains;

View file

@ -0,0 +1,37 @@
#!/usr/bin/env python3
import requests
import re
import json
res = requests.get('https://api.krispykreme.com/shops/?latitude=44.7601&longitude=-93.2748&count=1000&shopFeatureFlags=0&includeGroceryStores=false&includeShops=true')
locations = json.loads(res.text)
stores = []
for location in locations:
stores.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [float(location['longitude']), float(location['latitude'])], # yes, [lon, lat] since it's [x, y]
},
"properties": {
'address': location['address1'].title(),
'city': location['city'].title(),
'state': location['state'],
'zip': location['zipCode'],
'website': location['pagesUrl'],
},
})
geojson = {
"type": "FeatureCollection",
"features": stores,
}
with open("krispy-kreme-data.geojson", "w") as f:
f.write(json.dumps(geojson))
print(f"{len(locations)} locations found")

View file

@ -0,0 +1,24 @@
import VectorLayer from 'ol/layer/Vector';
import {Vector as VectorSource} from 'ol/source.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import {Style} from 'ol/style.js';
import Icon from 'ol/style/Icon.js';
import waffleHouseURL from '/data/krispy-kreme-data.geojson?url'; // TODO: remove `?url`?
import pinURL from './pin.svg?url'; // TODO: remove `?url`?
const vectorLayer = new VectorLayer({
source: new VectorSource({
url: waffleHouseURL,
format: new GeoJSON,
}),
style: new Style({
image: new Icon({
anchor: [0.5, 1],
src: pinURL,
}),
}),
});
export default vectorLayer;

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 384 512"
width="40px"
height="30px"
version="1.1"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 0,279.4 0,192 0,85.96 85.96,0 192,0 245.96029,0 294.73775,22.275796 329.62577,58.136607 363.27205,92.721042 384,139.94065 384,192 Z"
style="fill:#046a38;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 851 B

View file

@ -0,0 +1,23 @@
See also https://chandlerswift.com/2022/04/16/the-holy-trinity.html
```python
# Kwik Trip
# Export to CSV from https://www.kwiktrip.com/Maps-Downloads/Store-List
print("Searching for Kwik Trip locations")
kwiktrip_count = 0
with open('stores.csv') as f:
reader = csv.DictReader(f)
for row in reader:
kwiktrip_count += 1
stores.append({
'chain': "Kwik Trip",
'lat': float(row['Latitude']),
'long': float(row['Longitude']),
'address': row['Address'].title(),
'city': row['City'].title(),
'state': row['State'],
'zip': row['Zip'],
'website': f"https://www.kwiktrip.com/locator/store?id={row['Store Number']}",
})
print(f"{kwiktrip_count} locations found")
```

View file

@ -0,0 +1,48 @@
#!/usr/bin/env python3
import requests
import json
from bs4 import BeautifulSoup
print("Searching for Kwik Trip locations")
response = requests.get('https://www.kwiktrip.com/Maps-Downloads/Store-List')
# HACK HACK HACK
soup = BeautifulSoup(response.text, 'html.parser')
table = soup.find('table') # there's only one, currently; no identifying ID or anything
headers = [th.get_text() for th in table.find('thead').find_all('th')]
# turn it into a reasonable dict
raw_stores = []
for row in table.find('tbody').find_all('tr'):
store = {}
for (header, cell) in zip(headers, row.find_all('td')):
store[header] = cell.get_text()
raw_stores.append(store)
print(f"""{len(raw_stores)} locations found""")
# turn _that_ into GeoJSON Features
stores = []
for store in raw_stores:
stores.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [float(store['Longitude']), float(store['Latitude'])], # yes, [lon, lat] since it's [x, y]
},
"properties": {
'address': store['Address'].title(),
'city': store['City'].title(),
'state': store['State'],
'zip': store['Zip'],
'website': f"https://www.kwiktrip.com/locator/store?id={store['Store Number']}",
},
})
geojson = {
"type": "FeatureCollection",
"features": stores,
}
with open("kwik-trip-data.geojson", "w") as f:
f.write(json.dumps(geojson))

View file

@ -0,0 +1,24 @@
import VectorLayer from 'ol/layer/Vector';
import {Vector as VectorSource} from 'ol/source.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import {Style} from 'ol/style.js';
import Icon from 'ol/style/Icon.js';
import kwikTripURL from '/data/kwik-trip-data.geojson?url'; // TODO: remove `?url`?
import pinURL from './pin.svg?url'; // TODO: remove `?url`?
const vectorLayer = new VectorLayer({
source: new VectorSource({
url: kwikTripURL,
format: new GeoJSON,
}),
style: new Style({
image: new Icon({
anchor: [0.5, 1],
src: pinURL,
}),
}),
});
export default vectorLayer;

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 384 512"
width="40px"
height="30px"
version="1.1"
id="svg873"
sodipodi:docname="kwiktrip-location-pin-solid.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs877">
<inkscape:path-effect
effect="bspline"
id="path-effect6210"
is_visible="true"
lpeversion="1"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<inkscape:path-effect
effect="bspline"
id="path-effect6206"
is_visible="true"
lpeversion="1"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<inkscape:path-effect
effect="bspline"
id="path-effect5933"
is_visible="true"
lpeversion="1"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<inkscape:path-effect
effect="bspline"
id="path-effect5817"
is_visible="true"
lpeversion="1"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<mask
maskUnits="userSpaceOnUse"
id="mask5454">
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 -1.4e-6,279.4 -1.4e-6,192 -1.4e-6,85.96 85.959999,0 192,0 245.96029,0 294.73775,22.2758 329.62577,58.13661 363.27205,92.72104 384,139.94065 384,192 Z"
id="path5456"
sodipodi:nodetypes="sccssss"
style="fill:#ffffff;fill-opacity:1" />
</mask>
</defs>
<sodipodi:namedview
id="namedview875"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1255704"
inkscape:cx="-124.38138"
inkscape:cy="258.09136"
inkscape:window-width="1920"
inkscape:window-height="1080"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg873" />
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 0,279.4 0,192 0,85.96 85.96,0 192,0 245.96029,0 294.73775,22.275796 329.62577,58.136607 363.27205,92.721042 384,139.94065 384,192 Z"
id="path871"
sodipodi:nodetypes="sccssss"
style="fill:#c8102e;fill-opacity:1" />
<g
id="g838"
transform="matrix(0.73027096,0,0,0.73027096,52.207956,54.279026)">
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.270601"
d="m 131.77301,84.822086 c 0,0 -48.244176,-0.655215 -48.244176,-0.655215 0,0 -59.822086,175.480979 -59.822086,175.480979 m 0,0 c 0,0 49.349694,0.85276 49.349694,0.85276 0,0 16.217178,-52.27484 16.217178,-52.27484 0,0 31.87975,-28.89325 31.87975,-28.89325 0,0 6.63681,1.05766 6.63681,1.05766 0,0 12.96074,78.88712 12.96074,78.88712 0,0 49.16196,-0.42454 49.16196,-0.42454 0,0 -18.9276,-117.53006 -18.9276,-117.53006 0,0 60.87362,-55.928835 60.87362,-55.928835 0,0 -61.62945,-0.866258 -61.62945,-0.866258 0,0 -58.26258,65.289573 -58.26258,65.289573 0,0 -3.38527,-2.31779 -3.38527,-2.31779 0,0 23.19141,-62.677304 23.19141,-62.677304"
id="path6204"
inkscape:path-effect="#path-effect6206"
inkscape:original-d="M 131.77301,84.822086 83.528834,84.166871 23.706748,259.64785 m 0,0 49.349694,0.85276 16.217178,-52.27484 31.87975,-28.89325 6.63681,1.05766 12.96074,78.88712 49.16196,-0.42454 -18.9276,-117.53006 60.87362,-55.928835 -61.62945,-0.866258 -58.26258,65.289573 -3.38527,-2.31779 23.19141,-62.677304" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0"
d="m 260.7411,174.81902 c 0,0 -55.8049,-0.24111 -55.8049,-0.24111 0,0 9.69141,-29.53006 9.69141,-29.53006 0,0 148.77974,2.72506 148.77974,2.72506 0,0 -10.45608,28.08411 -10.45608,28.08411 0,0 -49.78553,-0.0642 -49.78553,-0.0642 0,0 -39.56674,142.51108 -39.56674,142.51108 0,0 -42.44376,-0.31234 -42.44376,-0.31234 0,0 39.58586,-143.17254 39.58586,-143.17254 z"
id="path6208"
inkscape:path-effect="#path-effect6210"
inkscape:original-d="m 260.7411,174.81902 -55.8049,-0.24111 9.69141,-29.53006 148.77974,2.72506 -10.45608,28.08411 -49.78553,-0.0642 -39.56674,142.51108 -42.44376,-0.31234 z"
sodipodi:nodetypes="ccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -0,0 +1,58 @@
#!/usr/bin/env python3
import requests
import re
import json
# Stolen from my machine, appears to work; sufficient and necessary to get
# around their firewall apparently? Last time this didn't work, so I wonder if
# their protections get stronger as I make more requests. Hopefully not!
UA={
"User-Agent": 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0'
}
res = requests.get('https://www.menards.com/store-details/locator.html', headers=UA)
content = res.text
# # For debugging, only fetch the above once; then cache it:
# with open('temp.txt', 'w') as f:
# f.write(content)
# ...and re-open it each time
# with open('temp.txt') as f:
# content = f.read()
try:
raw_initial_store_data = re.search(r'data-initial-stores="([^"]*)"', content)[1]
except:
print(content)
raise SystemExit
menardses = json.loads(raw_initial_store_data.replace("&quot;", '"'))
stores = []
for menards in menardses:
stores.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [float(menards['longitude']), float(menards['latitude'])], # yes, [lon, lat] since it's [x, y]
},
"properties": {
'address': menards['street'].title(),
'city': menards['city'].title(),
'state': menards['state'],
'zip': menards['zip'],
'website': f"https://www.menards.com/store-details/store.html?store={menards['number']}",
},
})
geojson = {
"type": "FeatureCollection",
"features": stores,
}
with open("menards-data.geojson", "w") as f:
f.write(json.dumps(geojson))
print(f"{len(menardses)} locations found")

View file

@ -0,0 +1,24 @@
import VectorLayer from 'ol/layer/Vector';
import {Vector as VectorSource} from 'ol/source.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import {Style} from 'ol/style.js';
import Icon from 'ol/style/Icon.js';
import menardsURL from '/data/menards-data.geojson?url'; // TODO: remove `?url`?
import pinURL from './pin.svg?url'; // TODO: remove `?url`?
const vectorLayer = new VectorLayer({
source: new VectorSource({
url: menardsURL,
format: new GeoJSON,
}),
style: new Style({
image: new Icon({
anchor: [0.5, 1],
src: pinURL,
}),
}),
});
export default vectorLayer;

View file

@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 384 512"
width="40px"
height="30px"
version="1.1"
id="svg873"
sodipodi:docname="menards-location-pin-solid.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs877">
<mask
maskUnits="userSpaceOnUse"
id="mask5454">
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 -1.4e-6,279.4 -1.4e-6,192 -1.4e-6,85.96 85.959999,0 192,0 245.96029,0 294.73775,22.2758 329.62577,58.13661 363.27205,92.72104 384,139.94065 384,192 Z"
id="path5456"
sodipodi:nodetypes="sccssss"
style="fill:#ffffff;fill-opacity:1" />
</mask>
</defs>
<sodipodi:namedview
id="namedview875"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1255704"
inkscape:cx="130.15623"
inkscape:cy="299.84798"
inkscape:window-width="1916"
inkscape:window-height="1030"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg873" />
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 0,279.4 0,192 0,85.96 85.96,0 192,0 245.96029,0 294.73775,22.275796 329.62577,58.136607 363.27205,92.721042 384,139.94065 384,192 Z"
id="path871"
sodipodi:nodetypes="sccssss"
style="fill:#009a3d;fill-opacity:1" />
<image
width="310.314"
height="310.31412"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,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
"
id="image2008"
x="34.402168"
y="26.948332"
mask="url(#mask5454)" />
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,43 @@
#!/usr/bin/env python3
import requests
import json
from bs4 import BeautifulSoup
print("Searching for Milwaukee Burger Company locations")
# Stolen from my machine, appears to work; sufficient and necessary to get
# around their firewall apparently?
UA={
"User-Agent": 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0'
}
response = requests.get('https://milwaukeeburgercompany.com/', headers=UA)
soup = BeautifulSoup(response.text, 'html.parser')
location_lis = soup.select('section#locations ul.location-list > li')
locations = []
for location_li in location_lis:
lon = location_li
locations.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [float(location_li['data-lng']), float(location_li['data-lat'])], # yes, [lon, lat] since it's [x, y]
},
"properties": {
'address': location_li.find('dd', class_="street").text,
'city': location_li.find('dd', class_="city-state-zip").text.split(',')[0],
'state': location_li.find('dd', class_="city-state-zip").text.split(', ')[1].split(' ')[0],
'zip': location_li.find('dd', class_="city-state-zip").text.split(' ')[-1],
},
})
geojson = {
"type": "FeatureCollection",
"features": locations,
}
with open("milwaukee-burger-company-data.geojson", "w") as f:
f.write(json.dumps(geojson))

View file

@ -0,0 +1,24 @@
import VectorLayer from 'ol/layer/Vector';
import {Vector as VectorSource} from 'ol/source.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import {Style} from 'ol/style.js';
import Icon from 'ol/style/Icon.js';
import url from '/data/milwaukee-burger-company-data.geojson?url'; // TODO: remove `?url`?
import pin from './pin.svg?url'; // TODO: remove `?url`?
const vectorLayer = new VectorLayer({
source: new VectorSource({
url: url,
format: new GeoJSON,
}),
style: new Style({
image: new Icon({
anchor: [0.5, 1],
src: pin,
}),
}),
});
export default vectorLayer;

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 384 512"
width="40px"
height="30px"
version="1.1"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 0,279.4 0,192 0,85.96 85.96,0 192,0 245.96029,0 294.73775,22.275796 329.62577,58.136607 363.27205,92.721042 384,139.94065 384,192 Z"
style="fill:#000;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 848 B

View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import requests
import re
import json
res = requests.get('https://locations.wafflehouse.com/')
content = res.text
locations = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*)</script>', content)[1]
locations = json.loads(locations)['props']['pageProps']['locations']
stores = []
for location in locations:
stores.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [float(location['longitude']), float(location['latitude'])], # yes, [lon, lat] since it's [x, y]
},
"properties": {
'address': location['addressLines'][0].title(),
'city': location['city'].title(),
'state': location['state'],
'zip': location['postalCode'],
'website': location['websiteURL'],
},
})
geojson = {
"type": "FeatureCollection",
"features": stores,
}
with open("waffle-house-data.geojson", "w") as f:
f.write(json.dumps(geojson))
print(f"{len(locations)} locations found")

View file

@ -0,0 +1,24 @@
import VectorLayer from 'ol/layer/Vector';
import {Vector as VectorSource} from 'ol/source.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import {Style} from 'ol/style.js';
import Icon from 'ol/style/Icon.js';
import waffleHouseURL from '/data/waffle-house-data.geojson?url'; // TODO: remove `?url`?
import pinURL from './pin.svg?url'; // TODO: remove `?url`?
const vectorLayer = new VectorLayer({
source: new VectorSource({
url: waffleHouseURL,
format: new GeoJSON,
}),
style: new Style({
image: new Icon({
anchor: [0.5, 1],
src: pinURL,
}),
}),
});
export default vectorLayer;

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 384 512"
width="40px"
height="30px"
version="1.1"
id="svg873"
sodipodi:docname="menards-location-pin-solid.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs877">
<mask
maskUnits="userSpaceOnUse"
id="mask5454">
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 -1.4e-6,279.4 -1.4e-6,192 -1.4e-6,85.96 85.959999,0 192,0 245.96029,0 294.73775,22.2758 329.62577,58.13661 363.27205,92.72104 384,139.94065 384,192 Z"
id="path5456"
sodipodi:nodetypes="sccssss"
style="fill:#ffffff;fill-opacity:1" />
</mask>
</defs>
<sodipodi:namedview
id="namedview875"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1255704"
inkscape:cx="130.15623"
inkscape:cy="299.84798"
inkscape:window-width="1916"
inkscape:window-height="1030"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg873" />
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 384,192 c 0,87.4 -117,243 -168.3,307.2 -12.3,15.3 -35.1,15.3 -47.4,0 C 116.1,435 0,279.4 0,192 0,85.96 85.96,0 192,0 245.96029,0 294.73775,22.275796 329.62577,58.136607 363.27205,92.721042 384,139.94065 384,192 Z"
id="path871"
sodipodi:nodetypes="sccssss"
style="fill:#fff200;fill-opacity:1" />
<image
width="310.314"
height="310.31412"
preserveAspectRatio="none"
xlink:href="data:image/webp;base64,UklGRiIFAABXRUJQVlA4TBUFAAAvH8AHECI51rad1vPEqGzbtm3btm3bxm/btm3bsW1z/4j6XaXOIDyOvVb6DOFUWakyhNNlZQBpnZwBpM4M3insAeT0mYAsSJJN26prfdm2bdv2vc+2bdu2bduHz7Zt2wOpkSRJctoj76qs3n0vgZUiCgABVj6AbXt9bc1bsr24E2yRzWayEZn/Cda+bftNAPtYBeQAeoDGga8BWwAaAdQBwlIKkAU0WyPaH4/rlOYMbvNHHPOHjxn9y4AmKZ+jPwAaAgSnBCAIaKZ3Q9g441Bb/1ge+jhqHjv1stdcDqrHZPdHZvlaPq5UDreXgI7kAbKLeNhjgz1Fcf3S2TosD6fgu+7sP9wQXBUc/S7u553x3qK64VDffG2enYC2k/O2pK/9jXx2uJ1fMQ/TLcG9FwWnBURXBDsXDaODS3FDAVwlDRBays8+hT8+etR/ZgxsGhud4dGW8KZsmV82zg3rZ7X4V+mSiMzqNGNwiXyayGR/0ti5JJYXoM0kAXYNDjSTY4fBgQIATMCtdPmHKxeUuI7l5eI3Q9WiW8WIcuaDOycgwwPJQvFMG+AAZUkAFPRr7nR2P2H3zMk/RljIzdwSc0vQu7EDorE9UdBtM6DVdUvkk0hEsuuG6taVy07PkwB0LrniMjo0bgkMDgRhuSBHPlcSCAILRaBWRcvniqNTgjMC/pJg/wex1a55IxKgJhFATIt8Y6m4nM47UtoYuD6N3cmvblS3HxVCAnfkh5NZk34QXRdcEBBR0D23a+kFdAZIZQK0zx2ebDTXoa9CG+cmbkzXsn7u43et1UmUtPrYtfToUPnL6kjT3FG2LRoBd8qe90lly6GxvaiuLpP6PPr7V4NUew8YYAJMLpvQrFSDaN6IwIHhdbmJMxg7QfeGtfdxymlThG2Lj8Mvw4WvbvrASwiK1o7L5fw1qnP4yARsffXUy1LRSDyhp+P6Ni907GcQAhdx34izWhjBke+BaOWUwM0ZnshCMUZ3MYA5JmBr91QKN5lM0y1x5HvT/VXblsjkWOJqJwSiJ4XcgsDtargheDemqwHMMgGNL53Q3tE1QdOsw+KEI5JcHrpUH0TjeySA/s2b75nkfSaZHm+CzGbuSfgzMT++hncIQGNMgJY5w5Kt5jrwWc4KTv0NJHqz54O5LOb1s/zOgRvZ+VgxZq0csxpnDq5fs0AmR4LGjlUrXm8BvUyAyKbZxkLxOJ519P8pL7oiuCog/7tOkFbfPSPqUh042bWBbLVFYvkBdAxIYmKBjiSWDcODg0Q8z/MiklgWuHYlSXxTcPyH+8Kd/IEzP2ClXLOHJkA5myggu2eD0dnzWKkCfeB50SdxvZTC6exJRJq7Apff+Try/o/M2qG588hupUdsEgFb2rvbrmVr9zNDH/g/xz64nUtGWgOT0RhzhrWTn4LvxVQ7IWQwQeP00ijrn+uFZ/oAAyhNCiCwsLveuJw7Ni+8Iu844t8c+GIczsqe9zjwBd7XcfCbCXhgIp6Adi0JypuLzKoEtMAm+X1xb/vufH7Irlk6WwvRZ2K8LKKyYbieX0obC9Alm0xAcm57u9fe1hTWLt1tw3R/8r3mjn2Xf+cFe98ZB20w2l3UNh5qm65VkxLQKpt8wESncrdh2lBa85gffOxVj6262aqHvfJjsOMjsWgtHlVK+thDQD2bku+BJsoF+v3Q1mlG/zZrcJg1KE3pXXpUx58s5nYH6GdTGlACGAQ2CpgGTAENAxoBcexjFQA=
"
id="image2008"
x="34.402168"
y="26.948332"
mask="url(#mask5454)" />
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB