Batch Geocoding
This is Pattern 1: server-side integration — your job or CLI holds the API key and calls PickPoint directly via the SDK. No REST wrapper needed. See the Integration guide for the full picture.
At ~100 ms per request, geocoding 10,000 addresses sequentially takes over 15 minutes. The official SDK batch helpers keep up to 20 requests in flight at all times — when one finishes, the next starts immediately (a conveyor, not fixed waves of 20). That cuts 10,000 addresses to about a minute.
Pass the full list of queries to the SDK — it manages concurrency, retries on transient errors, and auth refresh. Results are returned in the same order as the input. A soft 4xx for one address (e.g. no match) becomes an empty slot — the batch continues.
| Language | Batch API |
|---|---|
| JavaScript | pp.forward(queries[]) — pass an array of query objects |
| Python | await pp.forward_batch(queries[]) |
| Go | pp.ForwardBatch(ctx, queries[]) |
| Rust | pp.forward_batch(queries).await |
// batch_geocode.go
// go mod init yourapp && go get github.com/pickpoint/go-sdk
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/pickpoint/go-sdk/pickpoint"
)
func main() {
pp, err := pickpoint.New(pickpoint.Config{APIKey: os.Getenv("PICKPOINT_KEY")})
if err != nil {
panic(err)
}
addresses := []string{"Eiffel Tower, Paris", "Big Ben, London", "Colosseum, Rome"}
queries := make([]pickpoint.Query, len(addresses))
for i, addr := range addresses {
queries[i] = pickpoint.Query{"q": addr, "limit": "1"}
}
// ForwardBatch — conveyor keeps up to 20 requests in flight
out, err := pp.ForwardBatch(context.Background(), queries)
if err != nil {
panic(err)
}
for i, addr := range addresses {
if len(out[i]) == 0 {
fmt.Fprintf(os.Stderr, "no result: %s\n", addr)
continue
}
b, _ := json.Marshal(out[i][0])
fmt.Printf("[%d] %s → %s\n", i, addr, b)
}
}// batch-geocode.mjs
// npm install @pickpoint/sdk
import { writeFileSync } from 'fs';
import { PickPoint } from '@pickpoint/sdk';
const pp = new PickPoint({ apiKey: process.env.PICKPOINT_KEY });
const addresses = ['Eiffel Tower, Paris', 'Big Ben, London', 'Colosseum, Rome'];
// Pass an array — SDK keeps up to 20 requests in flight (conveyor, not fixed waves)
const queries = addresses.map(q => ({ q, limit: '1' }));
const results = await pp.forward(queries);
writeFileSync('geocoded.json', JSON.stringify(
addresses.map((address, i) => ({ address, result: results[i]?.[0] ?? null })),
null, 2,
));
console.log(`Done — ${results.filter(r => r?.length).length}/${addresses.length}`);# batch_geocode.py
# pip install pickpoint
import asyncio
import json
import os
from pickpoint import Client, Config
ADDRESSES = ["Eiffel Tower, Paris", "Big Ben, London", "Colosseum, Rome"]
async def main() -> None:
async with Client(Config(api_key=os.environ["PICKPOINT_KEY"])) as pp:
queries = [{"q": addr, "limit": "1"} for addr in ADDRESSES]
# forward_batch — conveyor keeps up to 20 requests in flight
results = await pp.forward_batch(queries)
rows = [{"address": a, "result": r[0] if r else None}
for a, r in zip(ADDRESSES, results)]
print(json.dumps(rows, indent=2))
if __name__ == "__main__":
asyncio.run(main())// Cargo.toml: pickpoint = "2", tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
use pickpoint::{query, Client, Config};
#[tokio::main]
async fn main() -> pickpoint::Result<()> {
let pp = Client::new(Config::with_api_key(
std::env::var("PICKPOINT_KEY").expect("PICKPOINT_KEY"),
))
.await?;
let addresses = vec!["Eiffel Tower, Paris", "Big Ben, London", "Colosseum, Rome"];
let queries: Vec<_> = addresses
.iter()
.map(|a| query([("q", *a), ("limit", "1")]))
.collect();
// forward_batch — conveyor keeps up to 20 requests in flight
let results = pp.forward_batch(queries).await?;
for (addr, slot) in addresses.iter().zip(&results) {
match slot.first() {
Some(v) => println!("{} → {}", addr, v),
None => eprintln!("no result: {}", addr),
}
}
Ok(())
}Throughput
With the SDK conveyor at 20 in-flight requests and ~100 ms average latency: ~200 req/sec, ~720k/hour.
| Addresses | Estimated time |
|---|---|
| 1,000 | ~5 seconds |
| 10,000 | ~1 minute |
| 100,000 | ~8–10 minutes |
| 1,000,000 | ~90 minutes |
Tips
- Cache. Store coordinates in a database — geocoding output for a given address is stable. Never re-geocode the same address twice.
- Clean addresses first. Add city and country when missing. Vague queries like "Main Street" return the wrong place.
- Handle empty slots. Batch geocoding returns an empty array for addresses that don't match. Log them for manual review or fall back to city-level geocoding.
- Add countrycodes. Pass
countrycodes=us(or equivalent) if all addresses are in one country — fewer false matches. - Reverse batch too. The same batch conveyor works for reverse and lookup:
pp.reverse_batch,pp.geocoding.batch.reverse,ReverseBatch,reverse_batch.
Integration guideGeocoding guideServer Integration
Integration guide — SDK packages, architecture patterns, security.