From 584b41c2a4937323c7744f7c450edd754933104c Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Sat, 20 Dec 2025 18:53:46 -0600 Subject: [PATCH] Add Costco layer --- layers/chains/costco/get_data.py | 54 ++++++++++++++++++++++++++++++++ layers/chains/costco/layer.js | 24 ++++++++++++++ layers/chains/costco/pin.svg | 18 +++++++++++ layers/chains/index.js | 5 +++ 4 files changed, 101 insertions(+) create mode 100755 layers/chains/costco/get_data.py create mode 100644 layers/chains/costco/layer.js create mode 100644 layers/chains/costco/pin.svg diff --git a/layers/chains/costco/get_data.py b/layers/chains/costco/get_data.py new file mode 100755 index 0000000..fd17d2b --- /dev/null +++ b/layers/chains/costco/get_data.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import requests +import json + +print("Searching for Costco locations") + +headers={ + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:146.0) Gecko/20100101 Firefox/146.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Upgrade-Insecure-Requests": "1", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Priority": "u=0, i", + "Pragma": "no-cache", + "Cache-Control": "no-cache" +} + +response = requests.get('https://ecom-api.costco.com/warehouseLocatorMobile/v1/warehouses.json?client_id=45823696-9189-482d-89c3-0c067e477ea1&latitude=0&longitude=0&limit=5000&distanceUnit=km', headers=headers).json() + +if not response["context"]["lastPage"]: + raise Exception("Got more than one page") + +stores = [] + +for w in response['warehouses']: + stores.append({ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [float(w['address']['longitude']), float(w['address']['latitude'])], # yes, [lon, lat] since it's [x, y] + }, + "properties": { + 'address': w['address']['line1'] + ' ' + w['address'].get('line2', ''), + 'city': w['address']['city'], + 'state': w['address'].get('territory', ''), + 'zip': w['address']['postalCode'], + 'type': 'Warehouse' if w['subType']['code'] == 'No Minor Type' else w['subType']['code'], + 'phone': w.get('phone', ''), + 'opening date': w['openingDate'], + }, + }) + +print(f"""{len(stores)} locations found""") + +geojson = { + "type": "FeatureCollection", + "features": stores, +} + +with open("data.geojson", "w") as f: + f.write(json.dumps(geojson)) diff --git a/layers/chains/costco/layer.js b/layers/chains/costco/layer.js new file mode 100644 index 0000000..af140b4 --- /dev/null +++ b/layers/chains/costco/layer.js @@ -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.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; diff --git a/layers/chains/costco/pin.svg b/layers/chains/costco/pin.svg new file mode 100644 index 0000000..cf90766 --- /dev/null +++ b/layers/chains/costco/pin.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/layers/chains/index.js b/layers/chains/index.js index e58f4ae..ae49201 100644 --- a/layers/chains/index.js +++ b/layers/chains/index.js @@ -1,3 +1,4 @@ +import costcoLayer from './costco/layer.js'; import countryKitchenLayer from './country-kitchen/layer.js'; import culversLayer from './culvers/layer.js'; import gilibertosLayer from './gilibertos/layer.js'; @@ -15,6 +16,10 @@ import zorbazLayer from './zorbaz/layer.js'; const chains = { name: "Chains", layers: [ + { + name: "Costco", + layer: costcoLayer, + }, { name: "Country Kitchen", layer: countryKitchenLayer,