Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: pip
directories:
- "/"
- "/*/python"
schedule:
interval: monthly
- package-ecosystem: maven
directory: "/distance-matrix/java"
schedule:
interval: monthly
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
97 changes: 97 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: CI

on:
push:
branches: [master]
pull_request:

jobs:
python:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sample:
- stores-import
- stores-sync
- stores-export
- batch-geocoding
- distance-matrix
- opening-hours
- datasets
- isochrone-stores
- static-map
- geolocation-stores
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install
working-directory: ${{ matrix.sample }}/python
run: |
pip install --quiet -r ../../requirements-dev.txt
if [ -f requirements.txt ]; then pip install --quiet -r requirements.txt; fi
- name: Lint
working-directory: ${{ matrix.sample }}/python
run: |
ruff check .
ruff format --check .
- name: Test
working-directory: ${{ matrix.sample }}/python
run: python -m pytest -q

node:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sample: [stores-import, stores-sync, distance-matrix]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Test
working-directory: ${{ matrix.sample }}/node
run: node --test

java:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- name: Compile
working-directory: distance-matrix/java
run: mvn --quiet --batch-mode compile

# Runs the read-only samples against a real project when the secret is configured.
live:
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs: [python]
env:
WOOSMAP_PRIVATE_KEY: ${{ secrets.WOOSMAP_PRIVATE_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Skip without a key
if: env.WOOSMAP_PRIVATE_KEY == ''
run: echo "WOOSMAP_PRIVATE_KEY secret not set, skipping live checks"
- name: Export stores
if: env.WOOSMAP_PRIVATE_KEY != ''
run: |
pip install --quiet -r stores-export/python/requirements.txt
python stores-export/python/export_stores.py --output /tmp/stores.json
- name: Geocode two rows
if: env.WOOSMAP_PRIVATE_KEY != ''
run: |
head -3 data/addresses_au.csv > /tmp/two.csv
python batch-geocoding/python/geocode_csv.py /tmp/two.csv /tmp/two.out.csv \
--address-columns addressline1,postalcode,town --country-column IsoCode
grep -c ROOFTOP /tmp/two.out.csv || true
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
__pycache__/
.pytest_cache/
.ruff_cache/
.venv/
venv/
node_modules/
target/
.idea/
.vscode/
.DS_Store
*.pyc
.env
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2016-2026 Woosmap

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,68 @@
Useful samples to work with woosmap APIs
# Woosmap samples

Server-side scripts that show how to do one job with the [Woosmap APIs](https://developers.woosmap.com).
Each folder is a use case. Inside, one folder per language. Copy the folder you need, every script stands alone.

| Use case | What it does | Python | Node |
| --- | --- | --- | --- |
| [stores-import](stores-import/) | Load a CSV, XLSX or Google Sheet into a project with one atomic replace | ✓ | ✓ (CSV, Sheets) |
| [stores-sync](stores-sync/) | Nightly sync: create, update and delete only the stores that changed | ✓ | ✓ |
| [stores-export](stores-export/) | Dump a project as re-importable Woosmap JSON or GeoJSON | ✓ | |
| [opening-hours](opening-hours/) | Turn a weekday-per-column spreadsheet into the `openingHours` object | ✓ | |
| [batch-geocoding](batch-geocoding/) | Geocode or reverse geocode a CSV with Localities | ✓ | |
| [distance-matrix](distance-matrix/) | Large matrices with the async endpoint, small ones in Java | ✓ | ✓ |
| [isochrone-stores](isochrone-stores/) | Which stores are within N minutes of an address | ✓ | |
| [datasets](datasets/) | Declare, import and query a Datasets API dataset | ✓ | |
| [static-map](static-map/) | Render a map image server-side for an e-mail or a PDF | ✓ | |
| [geolocation-stores](geolocation-stores/) | Nearest stores from a visitor's IP address | ✓ | |

Front-end samples for Map JS live in [js-samples](https://github.com/Woosmap/js-samples).

## Running a sample

Every script reads the private key from the `WOOSMAP_PRIVATE_KEY` environment variable and never
writes it to disk. Get one from the Console, on a project you can afford to overwrite.

```sh
export WOOSMAP_PRIVATE_KEY=...
cd stores-import/python
pip install -r requirements.txt
python import_stores.py ../../data/foodmarkets.csv --dry-run
```

Node samples need Node 20 or later and no dependency:

```sh
cd stores-import/node
node import-stores.mjs ../../data/foodmarkets.csv --dry-run
```

Test data lives in [data/](data/). The food markets set is small enough to import into any project.

## Conventions

- Python 3.10+, type hints everywhere, `requests` as the only HTTP dependency.
- Node 20+, ES modules, the built-in `fetch`, no dependency.
- One retry policy: 429 waits for the reset time in the `RateLimit` header (falling back to the
legacy `ratelimit-reset`), then retries. Anything else fails with the response body.
- Write operations use `/stores/replace` or explicit create, update and delete, never delete-then-post.
- Each sample ships its tests. `pytest` and `node --test` run offline against mocked responses.
- Where a sample needs plumbing that is not about Woosmap, it sits in its own module next to the main one.

## Contributing

Run the checks before opening a pull request:

```sh
pip install -r requirements-dev.txt
ruff check . && ruff format --check .
(cd stores-import/python && python -m pytest)
(cd stores-import/node && node --test)
```

CI runs the same for every sample, plus a compile of the Java client. When the repository secret
`WOOSMAP_PRIVATE_KEY` is set, it also runs the read-only samples against a real project.

## Licence

[MIT](LICENSE).
24 changes: 24 additions & 0 deletions batch-geocoding/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Geocode a CSV

Add coordinates to a file of addresses, or addresses to a file of coordinates, with the
[Localities geocode endpoint](https://developers.woosmap.com/products/localities/features/geocoding/).
Every input column is kept; six `geocode_*` columns are appended: lat, lng, formatted address, location
type (`ROOFTOP`, `GEOMETRIC_CENTER`, `APPROXIMATE`), public id and error.

```sh
pip install -r python/requirements.txt

# forward: pick the columns that make up the address, and the country when you know it
python python/geocode_csv.py ../data/addresses_au.csv out.csv \
--address-columns addressline1,postalcode,town --country-column IsoCode

# reverse
python python/geocode_csv.py ../data/coordinates.csv out.csv --reverse --lat-column lat --lng-column lng
```

Pass `--country fr` for a single country, `--language` for the output language and `--delay 0.1` to
pace requests. Rows that fail keep their input and get the reason in `geocode_error`, the run continues.
Always restrict the country when you can, it is the single biggest accuracy lever.

Check `geocode_location_type` before trusting a result: `GEOMETRIC_CENTER` on a street-level input means
the house number was not found.
Loading
Loading