A delivery app I consulted for was using Google Maps APIs for everything. Distance calculations, address validation, timezone lookups—all Google.
Their monthly bill: $4,200.
For a startup doing 50,000 deliveries a month, that's $0.08 per delivery just for location math. The actual delivery cost was $5. Their location APIs were 1.6% of gross revenue, doing nothing but basic math.
We replaced most of it with simpler APIs. Same functionality. Monthly cost: $180.
You don't need Google Maps for distance calculations. You don't need Google for geocoding basic addresses. And you definitely don't need Google to look up what timezone a city is in.
Distance: The Haversine Formula
The distance between two points on Earth is just math. The Haversine formula has been around since the 1800s. There's nothing proprietary about it.
async function getDistance(from, to) {
const res = await fetch(
`https://api.apiverve.com/v1/distancecalculator?lat1=${from.lat}&lon1=${from.lon}&lat2=${to.lat}&lon2=${to.lon}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data } = await res.json();
return {
kilometers: data.kilometers,
miles: data.miles,
nauticalMiles: data.nauticalMiles
};
}
// Usage
const distance = await getDistance(
{ lat: 40.7128, lon: -74.0060 }, // New York
{ lat: 34.0522, lon: -118.2437 } // Los Angeles
);
// { kilometers: 3935.75, miles: 2445.56, nauticalMiles: 2125.27 }
That's crow-flies distance. For delivery or driving estimates, multiply by a road factor (typically 1.2-1.4 for urban areas, 1.1-1.2 for highways).
function estimateDrivingDistance(straightLine) {
const roadFactor = 1.3; // Average for mixed urban/highway
return {
estimated: straightLine * roadFactor,
low: straightLine * 1.1,
high: straightLine * 1.5
};
}
Batch Distance Calculations
"Find the nearest store" is a common feature. Calculate distances to all locations and sort:
async function findNearestStores(userLocation, stores, limit = 5) {
// Calculate distances in parallel
const storesWithDistance = await Promise.all(
stores.map(async store => {
const distance = await getDistance(userLocation, {
lat: store.latitude,
lon: store.longitude
});
return {
...store,
distance: distance.miles
};
})
);
// Sort by distance and return nearest
return storesWithDistance
.sort((a, b) => a.distance - b.distance)
.slice(0, limit);
}
// Usage
const nearest = await findNearestStores(
{ lat: 40.7128, lon: -74.0060 }, // User in NYC
allStores,
3 // Top 3 nearest
);
// [
// { name: "Manhattan Store", distance: 2.3 },
// { name: "Brooklyn Store", distance: 4.1 },
// { name: "Queens Store", distance: 7.8 }
// ]
For large datasets (thousands of stores), calculate distances server-side and cache results by region. Don't make users wait for 5,000 API calls.
Geocoding: Addresses to Coordinates
Users enter addresses. APIs want coordinates. Geocoding bridges the gap.
async function geocodeAddress(address) {
const res = await fetch(
`https://api.apiverve.com/v1/geocoding?address=${encodeURIComponent(address)}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data } = await res.json();
return {
formattedAddress: data.formattedAddress,
lat: data.latitude,
lon: data.longitude,
confidence: data.confidence,
components: {
street: data.street,
city: data.city,
state: data.state,
country: data.country,
postalCode: data.postalCode
}
};
}
// Usage
const location = await geocodeAddress("1600 Pennsylvania Ave, Washington DC");
// {
// formattedAddress: "1600 Pennsylvania Avenue NW, Washington, DC 20500",
// lat: 38.8977,
// lon: -77.0365,
// confidence: 0.98,
// components: { ... }
// }
The confidence score matters. Low confidence means the address might be ambiguous or partial—you may want to ask the user to confirm.
Reverse Geocoding: Coordinates to Addresses
User grants location permission. You get coordinates. Now what?
async function reverseGeocode(lat, lon) {
const res = await fetch(
`https://api.apiverve.com/v1/reversegeocode?lat=${lat}&lon=${lon}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data } = await res.json();
return {
address: data.formattedAddress,
street: data.street,
city: data.city,
state: data.state,
country: data.country,
postalCode: data.postalCode
};
}
// Browser geolocation -> human-readable address
navigator.geolocation.getCurrentPosition(async (position) => {
const location = await reverseGeocode(
position.coords.latitude,
position.coords.longitude
);
console.log(`You're near ${location.address}`);
});
Great for "confirm your location" flows. Show users their detected address and let them correct if needed.
ZIP Code Lookup
Sometimes you just need city and state from a ZIP code:
async function lookupZipCode(zip) {
const res = await fetch(
`https://api.apiverve.com/v1/zipcodes?zipcode=${zip}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data } = await res.json();
return {
zipCode: data.zipCode,
city: data.city,
state: data.state,
stateCode: data.stateCode,
county: data.county,
timezone: data.timezone,
coordinates: {
lat: data.latitude,
lon: data.longitude
}
};
}
// Auto-fill city/state from ZIP
const zipInput = document.querySelector('#zip');
zipInput.addEventListener('change', async (e) => {
if (e.target.value.length === 5) {
const location = await lookupZipCode(e.target.value);
document.querySelector('#city').value = location.city;
document.querySelector('#state').value = location.stateCode;
}
});
Auto-filling city and state from ZIP code is one of those tiny UX improvements that users notice and appreciate.
Timezone Handling
Time is hard. Timezones are harder. And daylight saving makes it worse.
async function getTimezone(lat, lon) {
const res = await fetch(
`https://api.apiverve.com/v1/timezonelookup?lat=${lat}&lon=${lon}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data } = await res.json();
return {
timezone: data.timezone, // "America/New_York"
abbreviation: data.abbreviation, // "EST" or "EDT"
utcOffset: data.utcOffset, // -5 or -4
isDST: data.isDST, // true/false
currentTime: data.currentTime
};
}
// Convert time to user's timezone
function formatTimeForUser(utcTime, userTimezone) {
return new Date(utcTime).toLocaleString('en-US', {
timeZone: userTimezone,
hour: 'numeric',
minute: '2-digit',
hour12: true
});
}
// Usage
const meeting = "2026-01-24T15:00:00Z"; // 3pm UTC
const userTz = await getTimezone(40.7128, -74.0060); // NYC
formatTimeForUser(meeting, userTz.timezone);
// "10:00 AM" (EST)
Building a Delivery Estimation System
Let's combine everything into something practical—delivery time estimates:
class DeliveryEstimator {
constructor() {
this.averageSpeed = 30; // mph in urban areas
}
async estimateDelivery(origin, destination) {
// Get coordinates if addresses provided
const [originCoords, destCoords] = await Promise.all([
typeof origin === 'string' ? this.geocode(origin) : origin,
typeof destination === 'string' ? this.geocode(destination) : destination
]);
// Calculate distance
const distance = await this.getDistance(originCoords, destCoords);
// Estimate driving distance (straight line * road factor)
const drivingDistance = distance.miles * 1.3;
// Estimate time
const drivingHours = drivingDistance / this.averageSpeed;
const drivingMinutes = Math.round(drivingHours * 60);
// Get timezone for destination (for delivery window display)
const timezone = await this.getTimezone(destCoords.lat, destCoords.lon);
// Calculate delivery window
const now = new Date();
const deliveryStart = new Date(now.getTime() + drivingMinutes * 60000);
const deliveryEnd = new Date(deliveryStart.getTime() + 30 * 60000); // 30 min window
return {
distance: {
straight: distance.miles,
estimated: drivingDistance
},
time: {
minutes: drivingMinutes,
display: this.formatDuration(drivingMinutes)
},
deliveryWindow: {
start: this.formatLocalTime(deliveryStart, timezone.timezone),
end: this.formatLocalTime(deliveryEnd, timezone.timezone),
timezone: timezone.abbreviation
}
};
}
async geocode(address) {
const res = await fetch(
`https://api.apiverve.com/v1/geocoding?address=${encodeURIComponent(address)}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data } = await res.json();
return { lat: data.latitude, lon: data.longitude };
}
async getDistance(from, to) {
const res = await fetch(
`https://api.apiverve.com/v1/distancecalculator?lat1=${from.lat}&lon1=${from.lon}&lat2=${to.lat}&lon2=${to.lon}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
return (await res.json()).data;
}
async getTimezone(lat, lon) {
const res = await fetch(
`https://api.apiverve.com/v1/timezonelookup?lat=${lat}&lon=${lon}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
return (await res.json()).data;
}
formatDuration(minutes) {
if (minutes < 60) return `${minutes} min`;
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
}
formatLocalTime(date, timezone) {
return date.toLocaleString('en-US', {
timeZone: timezone,
hour: 'numeric',
minute: '2-digit',
hour12: true
});
}
}
// Usage
const estimator = new DeliveryEstimator();
const estimate = await estimator.estimateDelivery(
"123 Warehouse St, Brooklyn NY",
"456 Customer Ave, Manhattan NY"
);
// {
// distance: { straight: 3.2, estimated: 4.16 },
// time: { minutes: 8, display: "8 min" },
// deliveryWindow: { start: "2:15 PM", end: "2:45 PM", timezone: "EST" }
// }
Caching Location Data
Location lookups are perfect for caching—ZIP codes don't change, city coordinates don't move.
class LocationCache {
constructor() {
this.zipCache = new Map();
this.geocodeCache = new Map();
this.timezoneCache = new Map();
}
async getZipCode(zip) {
if (this.zipCache.has(zip)) {
return this.zipCache.get(zip);
}
const data = await lookupZipCode(zip);
this.zipCache.set(zip, data);
return data;
}
async geocode(address) {
const key = address.toLowerCase().trim();
if (this.geocodeCache.has(key)) {
return this.geocodeCache.get(key);
}
const data = await geocodeAddress(address);
this.geocodeCache.set(key, data);
return data;
}
async getTimezone(lat, lon) {
// Round to 2 decimal places for cache key (0.01 degree ≈ 1km)
const key = `${lat.toFixed(2)},${lon.toFixed(2)}`;
if (this.timezoneCache.has(key)) {
return this.timezoneCache.get(key);
}
const data = await getTimezone(lat, lon);
this.timezoneCache.set(key, data);
return data;
}
}
ZIP codes can be cached forever. Geocoded addresses can be cached for days. Timezones can be cached with reduced precision (nearby points share timezones).
When You Actually Need Google Maps
Let's be honest about what these APIs don't do:
Turn-by-turn navigation. If you need actual driving directions with street names and turns, you need a routing API.
Real-time traffic. Crow-flies distance doesn't account for rush hour. For traffic-adjusted ETAs, you need traffic data.
Interactive maps. For displaying maps with pins and routes, you need a mapping library (though there are free/cheaper alternatives to Google Maps).
Street View. If you need imagery, that's proprietary.
For most location features—distance calculations, address validation, timezone handling, nearest-location queries—you don't need the Google tax.
The Cost Comparison
Let's do the math for that delivery app:
Google Maps pricing (2024):
- Geocoding: $5 per 1,000 requests
- Distance Matrix: $5-10 per 1,000 elements
- Timezone: $5 per 1,000 requests
50,000 deliveries × 4 API calls average = 200,000 calls/month = ~$1,000-4,000/month
APIVerve pricing:
- All location APIs: 1 credit each
- Pro plan: {{plan.pro.price}}/month for {{plan.pro.calls}} credits
Same 200,000 calls = $400/month with aggressive caching bringing it down further.
The delivery app I mentioned? They needed full routing for a subset of deliveries (complex multi-stop routes). So they use Google for that specific use case (~5% of trips) and APIVerve for everything else. Monthly savings: $3,000+.
Location features don't have to be expensive. Distance calculations, geocoding, timezone lookups—these are solved problems. You don't need enterprise pricing for basic math.
The Distance Calculator, Geocoding, Reverse Geocode, ZIP Code Lookup, and Timezone Lookup APIs handle the common cases. Same API key, consistent responses.
Get your API key and build location features without the enterprise bill.
Originally published at APIVerve Blog
Top comments (0)