All posts

How to Build Delivery Zones in Vietnam using Map APIs

A step-by-step developer tutorial on how to implement location-based service boundaries, calculate shipping fees, and filter regions in Vietnam.

If you are building an e-commerce store, a food-delivery app, or a shipping platform in Vietnam, one of the first problems you need to solve is: How do we outline our service coverage area and calculate shipping fees automatically?

Many teams start by calculating costs based on a simple straight-line radius (e.g. 2km, 5km). However, this method is highly inaccurate in Vietnamese urban centers due to rivers, bridges, one-way roads, and gridlocked traffic.

The industry standard, used by major platforms like Grab, Shopee, and Giaohangtietkiem, is segmenting delivery zones based on administrative boundaries (District and Ward boundaries).

This article provides a step-by-step guide to building a delivery zone system in code, using boundary datasets from GoGoDuk Map API and the Turf.js helper library.


The Delivery Zone System Architecture

A robust delivery zoning flow works in 4 steps:

  1. Define Service Zones: Retrieve the geographic boundary polygons for the districts or wards you intend to service.
  2. Normalize Customer Input: When a customer types their address at checkout, use autocomplete to suggest matching places and resolve the selection into a GPS coordinate (latitude/longitude).
  3. Point-in-Polygon Check: Run a geometric check to see which district boundary polygon contains the customer's coordinate.
  4. Calculate Pricing: Apply the pre-configured delivery fee associated with that matched boundary zone.
[Address Input] ──> [Geocoding API] ──> [GPS Coordinate]


[Apply Fee] <── [Match Delivery Zone] <── [Point-in-Polygon Check]

Step 1: Retrieve Vietnam Administrative Boundaries

Instead of licensing expensive boundary datasets, you can query GoGoDuk's /v1/admin-boundaries endpoint to fetch GeoJSON polygons for Vietnam's 63 provinces and 700+ districts for free.

For example, to fetch the boundaries of District 1, Ho Chi Minh City:

curl -H "X-API-Key: gdk_live_..." \
  "https://api.gogoduk.com/v1/admin-boundaries/resolve?district=Quan+1&province=Ho+Chi+Minh"

The response contains the geometry coordinates of the boundary:

{
  "name": "District 1",
  "province": "Thành phố Hồ Chí Minh",
  "geometry": {
    "type": "Polygon",
    "coordinates": [
      [
        [106.6974, 10.7852],
        [106.7041, 10.7811],
        ...
      ]
    ]
  }
}

Step 2: Autocomplete and Resolve Address to Coordinates

To avoid typos and address errors during checkout, use the autocomplete suggest endpoint to help users pick a valid Vietnamese address.

// Triggered on keyup events in the address input box
async function getSuggestions(input) {
  const res = await fetch(`https://api.gogoduk.com/v1/suggest?input=${encodeURIComponent(input)}`, {
    headers: { "X-API-Key": "gdk_live_..." }
  });
  const data = await res.json();
  return data.predictions; // List of structured place predictions
}

Once the user selects a place (exposing a placeId like gdk_place_12345), resolve it to get GPS coordinates:

async function resolveLocation(placeId) {
  const res = await fetch(`https://api.gogoduk.com/v1/resolve?id=${placeId}`, {
    headers: { "X-API-Key": "gdk_live_..." }
  });
  const data = await res.json();
  const { lat, lon } = data.result.location;
  return [lon, lat]; // Returns [longitude, latitude] for GeoJSON standard
}

Step 3: Run Point-in-Polygon Check

At this stage, you have the customer's location as a GPS coordinate Point = [lon, lat], and the district boundary as a Polygon. You need to evaluate whether the point lies inside the polygon.

You can perform this containment check at the database level (using PostgreSQL PostGIS ST_Contains) or inside your Node.js or browser application using the open-source Turf.js library.

Install Turf.js utilities:

npm install @turf/boolean-point-in-polygon @turf/helpers

Evaluate containment in JavaScript:

import pointInPolygon from "@turf/boolean-point-in-polygon";
import { point, polygon } from "@turf/helpers";
 
// Coordinate retrieved from GoGoDuk Resolve API (Ben Thanh Market)
const customerCoordinate = [106.6994, 10.7769];
const customerPoint = point(customerCoordinate);
 
// District 1 boundary coordinate array retrieved from GoGoDuk
const districtBoundaryGeometry = {
  "type": "Polygon",
  "coordinates": [
    [
      [106.6925, 10.7850],
      [106.7080, 10.7800],
      [106.7020, 10.7680],
      [106.6880, 10.7730],
      [106.6925, 10.7850] // Polygon closure coordinate
    ]
  ]
};
const districtPolygon = polygon(districtBoundaryGeometry.coordinates);
 
// Run the point-in-polygon containment check
const isInside = pointInPolygon(customerPoint, districtPolygon);
 
if (isInside) {
  console.log("Customer is within District 1 delivery coverage!");
  // Apply District 1 shipping fees (e.g. 15,000 VND)
} else {
  console.log("Address is outside our service area.");
}

Step 4: Automate Shipping Fee Calculations at Checkout

To compute shipping costs dynamically, store shipping rates mapped to administrative zones in your database:

Service Area (District ID)Delivery FeeExpected Delivery Time
District 115,000 VND15-30 mins
Binh Thanh District20,000 VND30-45 mins
District 730,000 VND45-60 mins

Here is a full checkout route implementation in Node.js:

app.post("/api/checkout/shipping-fee", async (req, res) => {
  try {
    const { placeId } = req.body;
    
    // 1. Resolve place ID to GPS coordinates
    const customerLocation = await resolveLocation(placeId);
    
    // 2. Query DB using PostGIS ST_Contains to identify the matching district
    const matchedZone = await db.query(
      `SELECT id, name, shipping_fee 
       FROM delivery_zones 
       WHERE ST_Contains(geom, ST_SetSRID(ST_Point($1, $2), 4326))`,
      [customerLocation[0], customerLocation[1]]
    );
 
    if (matchedZone.rows.length > 0) {
      const zone = matchedZone.rows[0];
      return res.json({
        success: true,
        district: zone.name,
        fee: zone.shipping_fee,
      });
    } else {
      return res.json({
        success: false,
        message: "Sorry, your address falls outside our delivery service area."
      });
    }
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Conclusion

Calculating shipping fees using real administrative boundary polygons instead of geometric distance circles eliminates disputes with delivery drivers, lowers checkout friction, and allows precise dispatch forecasting.

With GoGoDuk's regularly updated boundary dataset and Vietnamese-optimized autocomplete APIs, you can build a production-ready delivery dispatch system in a few hours.

Start building today for free by visiting the GoGoDuk API Documentation.

Want to use GoGoDuk?

Free forever — 100 requests/day per account, no credit card. Higher limits on request.

Sign up →