bd-map-explorer
v1.0.1
Published
Bangladesh interactive map — Division, Zila, Upazila, Union drill-down with region isolation
Maintainers
Readme
bd-map-explorer
Interactive Bangladesh map for React apps. Drill down through the full admin hierarchy — Division (বিভাগ) → Zila/District (জেলা) → Upazila (উপজেলা) → Union (ইউনিয়ন) — with automatic region isolation: only Bangladesh (or the selected area) stays visible on the map.
Table of contents
- Features
- Demo
- Installation
- Quick start
- How it works
- Components
- Hooks
- Boundary API
- Constants & utilities
- Framework guides
- Examples
- Data sources
- Troubleshooting
- Development
- License
Features
- Bangladesh-only view — India, Myanmar, and other countries are masked out
- 4-level drill-down — Division → Zila → Upazila → Union
- Accurate boundaries — Official upazila GeoJSON + union ADM4 data
- Region isolation — Each click zooms and masks to only that admin area
- Bilingual — English and Bangla labels in the sidebar (EN / BN toggle)
- Breadcrumb navigation — Jump back to any parent level
- Headless mode — Use
useMapHierarchy+IsolatedAreaMapwith your own UI - Area picker — Optional satellite map with polygon drawing (
AreaMapPicker) - Works with Vite, CRA, Next.js (client-side)
Demo
Live source & demo app: github.com/sadikworkbd5622/Bd-map
git clone https://github.com/sadikworkbd5622/Bd-map.git
cd Bd-map/demo
npm install
npm run devInstallation
npm install bd-map-explorer leaflet react-leafletPeer dependencies
Your app must already have (or install):
npm install react react-dom leaflet react-leaflet| Package | Version |
|---------|---------|
| react | >= 18 |
| react-dom | >= 18 |
| leaflet | ^1.9.4 |
| react-leaflet | ^5.0.0 |
Quick start
1. Import Leaflet CSS (required, once in your app entry):
import "leaflet/dist/leaflet.css";2. Render the full explorer:
import { MapExplorer, I18nProvider } from "bd-map-explorer";
export default function App() {
return (
<I18nProvider>
<div style={{ minHeight: "100vh", background: "#0b0c10" }}>
<MapExplorer />
</div>
</I18nProvider>
);
}That’s it. Users can select divisions, zilas, upazilas, and unions from the sidebar; the map updates automatically.
How it works
bd-map-explorer separates navigation state from map rendering. A single hook (useMapHierarchy) owns the admin selection and loads the right GeoJSON for each level. The map (IsolatedAreaMap) receives that geometry and applies a visual mask so only the active region remains visible.
Architecture
flowchart LR
subgraph UI["User interface"]
SB["MapExplorerSidebar"]
BC["Breadcrumb / EN·BN toggle"]
end
subgraph State["State & data"]
H["useMapHierarchy"]
LOC["Location lists\n(EN / BN JSON)"]
API["Boundary API\n(CDN + bundled ADM4)"]
end
subgraph Map["Map layer"]
IAM["IsolatedAreaMap"]
MASK["Dynamic mask"]
TILES["OpenStreetMap tiles"]
end
SB --> H
BC --> H
H --> LOC
H --> API
H -->|baseBoundary, activeRegion| IAM
IAM --> MASK
IAM --> TILES| Layer | Responsibility |
|-------|----------------|
| MapExplorerSidebar | Lists divisions, zilas, upazilas, and unions; drives selection handlers |
| useMapHierarchy | Tracks selected IDs, resolves labels, fetches boundaries, exposes activeRegion |
| IsolatedAreaMap | Renders tiles, draws the region outline, masks everything outside the focus area |
| Boundary API | Returns GeoJSON for Bangladesh, divisions, districts, upazilas, and unions |
Selection lifecycle
- User picks an admin unit in the sidebar (or via
handleSelectDivision,handleSelectDistrict, etc.). - The hook builds a selection key from the current division → zila → upazila → union path.
- Boundary data is fetched asynchronously from the CDN (divisions through upazilas) or from bundled ADM4 data (unions).
activeRegionupdates only when the loaded boundary matches the current selection, so fast clicks do not flash a parent region.- The map fits bounds and applies a mask so neighbouring countries and unselected areas are hidden.
Region isolation
Isolation is not a simple zoom. IsolatedAreaMap computes a dynamic mask from the focus polygon:
- No selection —
countryMaskBoundary(division-level polygons) keeps the view limited to Bangladesh. - Division / zila / upazila / union selected —
activeRegionbecomes the focus polygon; everything outside it is covered with a solid overlay (maskOpacity: 1by default). - Pan limits —
MapBoundsEnforcerkeeps the viewport within Bangladesh bounds. - Zoom —
fitMaxZoomscales with admin level (country → union) for a consistent framing.
What users see at each level
| User action | Map behavior | |-------------|--------------| | Initial load | Bangladesh only; India, Myanmar, and other neighbours masked | | Select division | Zoom to division; mask outside its boundary | | Select zila (district) | Zoom to district; parent division hidden | | Select upazila | Zoom to upazila; higher levels hidden | | Select union | Zoom to union; ADM4 boundary loaded on first use (~12 MB) | | Breadcrumb or Reset | Step up one level or return to full Bangladesh view |
Headless usage
You do not need MapExplorer for this flow. Use useMapHierarchy with your own UI and pass the returned boundaries into IsolatedAreaMap — the same state machine and masking logic apply.
Components
<MapExplorer />
All-in-one UI: sidebar + isolated map. Best for drop-in usage.
<MapExplorer className="optional-css-class" />| Prop | Type | Default | Description |
|------|------|---------|-------------|
| className | string | "" | Extra CSS class on the root container |
<IsolatedAreaMap />
Map component only — pair with your own sidebar or useMapHierarchy.
<IsolatedAreaMap
baseBoundary={h.baseBoundary}
countryMaskBoundary={h.countryMaskBoundary}
activeRegion={h.activeRegion}
fitMaxZoom={11}
showResetControl={true}
onReset={h.resetAll}
style={{ height: "100%", width: "100%" }}
/>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| baseBoundary | FeatureCollection | — | Full Bangladesh boundary (required) |
| countryMaskBoundary | FeatureCollection | — | Coarse mask for country-level view |
| activeRegion | FeatureCollection \| null | — | Currently selected region to show |
| maskOpacity | number | 1 | Outside mask opacity (0–1) |
| maskColor | string | #0b0c10 | Color outside selected region |
| fitMaxZoom | number | 13 | Max zoom when fitting bounds |
| showResetControl | boolean | false | Show “Zoom Out” button |
| onReset | function | — | Called when reset button clicked |
| mapKey | string | — | Force layer refresh on selection change |
| className | string | — | Wrapper class |
| style | object | — | Wrapper inline styles |
| children | ReactNode | — | Extra map overlays |
<MapExplorerSidebar />
Sidebar only — pass the full object from useMapHierarchy.
const mapHierarchy = useMapHierarchy();
<MapExplorerSidebar mapHierarchy={mapHierarchy} /><AreaMapPicker />
Satellite map with search, polygon drawing, and area calculation. Useful for land / plot selection UIs. Requires I18nProvider.
Hooks
useMapHierarchy()
Manages admin selection state and loads GeoJSON boundaries asynchronously.
import { useMapHierarchy } from "bd-map-explorer";
function MyComponent() {
const h = useMapHierarchy();
// ...
}Return value
| Property | Type | Description |
|----------|------|-------------|
| lang | "en" \| "bn" | Current language |
| setLang | function | Set language ("en" or "bn") |
| baseBoundary | FeatureCollection | Full Bangladesh GeoJSON |
| countryMaskBoundary | FeatureCollection | Division-level mask for country view |
| activeRegion | FeatureCollection \| null | Boundary matching current selection |
| isLoadingBoundary | boolean | true while boundary is loading |
| isRegionStale | boolean | true when map is catching up to selection |
| selectedDivisionId | string | Selected division ID |
| selectedDistrictId | string | Selected zila ID |
| selectedUpazilaId | string | Selected upazila ID |
| selectedUnionId | string | Selected union ID |
| availableDivisions | { value, title }[] | Divisions for current lang |
| availableDistricts | { value, title }[] | Zilas in selected division |
| availableUpazilas | { value, title }[] | Upazilas in selected zila |
| availableUnions | { value, title }[] | Unions in selected upazila |
| handleSelectDivision | (id) => void | Select division |
| handleSelectDistrict | (id) => void | Select zila |
| handleSelectUpazila | (id) => void | Select upazila |
| handleSelectUnion | (id) => void | Select union |
| resetAll | () => void | Clear selection → Bangladesh view |
Selection handlers
IDs come from bundled location data (locations.en.json / locations.bn.json). Example — Barisal division:
h.handleSelectDivision("10"); // Barisal
h.handleSelectDistrict("4"); // Barguna (within Barisal)useAreaBoundary() / usePolygonDrawing()
Lower-level hooks for AreaMapPicker — search Nominatim, draw polygons, compute area.
Boundary API
Async functions that return GeoJSON FeatureCollection objects.
import {
getBangladeshBoundary,
getBangladeshMaskBoundary,
getDivisionBoundary,
getDistrictBoundary,
getUpazilaBoundary,
getUnionBoundary,
} from "bd-map-explorer";getBangladeshBoundary()
All upazila polygons for Bangladesh.
const bd = await getBangladeshBoundary();getBangladeshMaskBoundary()
Division-level polygons — used for reliable country-only masking.
const mask = await getBangladeshMaskBoundary();getDivisionBoundary(divisionId)
// Barisal division (location id 10)
const barisal = await getDivisionBoundary("10");getDistrictBoundary(districtTitle, options)
const barguna = await getDistrictBoundary("Barguna", {
districtId: "4", // from location data
divisionId: "10", // parent division (recommended)
});getUpazilaBoundary(upazilaTitle, districtTitle, options)
const sadar = await getUpazilaBoundary("Barguna Sadar", "Barguna", {
divisionId: "10",
districtId: "4",
upazilaId: "1",
});getUnionBoundary(unionTitle, upazilaTitle, districtTitle, options)
const union = await getUnionBoundary(
"Union Name",
"Barguna Sadar",
"Barguna",
{ upazilaBoundary: upazilaGeoJson } // optional, improves accuracy
);Union boundaries use bundled ADM4 data (~12 MB, lazy-loaded on first use).
Constants & utilities
import {
BD_CENTER,
BD_BOUNDS,
DEFAULT_ZOOM,
FIT_ZOOM_BY_LEVEL,
MAP_MASK_COLOR,
MAP_TILE_URL,
REGION_OUTLINE_STYLE,
LOCATION_DIVISION_TO_GEO_ID,
BD_BOUNDARIES_URL,
buildDynamicMask,
computeDynamicBounds,
calculateGeodesicArea,
isPointInGeoJson,
searchNominatim,
} from "bd-map-explorer";FIT_ZOOM_BY_LEVEL
| Level | Max zoom |
|-------|----------|
| country | 7 |
| division | 9 |
| district | 11 |
| upazila | 14 |
| union | 17 |
Framework guides
Vite + React
No extra config. Import Leaflet CSS in main.jsx:
import "leaflet/dist/leaflet.css";
import { MapExplorer, I18nProvider } from "bd-map-explorer";Create React App
Same as Vite. Add Leaflet CSS to index.js or App.js.
Next.js (App Router)
Leaflet requires the browser. Use dynamic import with ssr: false:
"use client";
import dynamic from "next/dynamic";
import "leaflet/dist/leaflet.css";
import { I18nProvider } from "bd-map-explorer";
const MapExplorer = dynamic(
() => import("bd-map-explorer").then((m) => m.MapExplorer),
{ ssr: false, loading: () => <p>Loading map…</p> }
);
export default function MapPage() {
return (
<I18nProvider>
<main style={{ height: "100vh" }}>
<MapExplorer />
</main>
</I18nProvider>
);
}Next.js (Pages Router)
import dynamic from "next/dynamic";
import "leaflet/dist/leaflet.css";
const MapExplorer = dynamic(
() => import("bd-map-explorer").then((m) => m.MapExplorer),
{ ssr: false }
);Examples
Example 1 — Full explorer (simplest)
import "leaflet/dist/leaflet.css";
import { MapExplorer, I18nProvider } from "bd-map-explorer";
export default function MapPage() {
return (
<I18nProvider defaultLang="bn">
<MapExplorer />
</I18nProvider>
);
}Example 2 — Custom sidebar
import "leaflet/dist/leaflet.css";
import {
useMapHierarchy,
IsolatedAreaMap,
MapExplorerSidebar,
} from "bd-map-explorer";
export default function CustomLayout() {
const h = useMapHierarchy();
return (
<div style={{ display: "flex", height: "80vh" }}>
<MapExplorerSidebar mapHierarchy={h} />
<IsolatedAreaMap
baseBoundary={h.baseBoundary}
countryMaskBoundary={h.countryMaskBoundary}
activeRegion={h.activeRegion}
style={{ flex: 1 }}
/>
</div>
);
}Example 3 — Boundary data only (no React map)
import { getDistrictBoundary } from "bd-map-explorer";
const dhakaZila = await getDistrictBoundary("Dhaka", {
districtId: "18",
divisionId: "30",
});
console.log(dhakaZila.features.length, "upazila polygons");Example 4 — React to selection changes
import { useMapHierarchy } from "bd-map-explorer";
import { useEffect } from "react";
function LocationTracker() {
const {
selectedDivisionId,
selectedDistrictId,
availableDistricts,
handleSelectDistrict,
} = useMapHierarchy();
useEffect(() => {
if (selectedDivisionId === "30" && !selectedDistrictId) {
// Auto-select Dhaka when Dhaka division is picked
const dhaka = availableDistricts.find((d) => d.title === "Dhaka");
if (dhaka) handleSelectDistrict(dhaka.value);
}
}, [selectedDivisionId, selectedDistrictId, availableDistricts]);
return null;
}Data sources
| Data | Source | Bundled |
|------|--------|---------|
| Division / Zila / Upazila boundaries | bangladesh-geojson (CDN) | Fetched at runtime |
| Union boundaries (ADM4) | geoBoundaries simplified | Yes (unions-adm4.json) |
| Location lists (EN/BN) | Bangladesh admin hierarchy JSON | Yes (locations.en.json, locations.bn.json) |
Division location IDs map to GeoJSON via LOCATION_DIVISION_TO_GEO_ID:
| Division | Location ID | Geo ID | |----------|-------------|--------| | Barisal | 10 | 1 | | Chattogram | 20 | 2 | | Dhaka | 30 | 3 | | Khulna | 40 | 4 | | Mymensingh | 45 | 8 | | Rajshahi | 50 | 5 | | Rangpur | 55 | 6 | | Sylhet | 60 | 7 |
Troubleshooting
Map is blank / grey tiles
- Import Leaflet CSS:
import "leaflet/dist/leaflet.css" - Ensure the map container has a height (e.g.
height: 80vh)
window is not defined (Next.js)
- Use
dynamic(..., { ssr: false })— see Next.js guide
Neighbouring countries visible
- Use
countryMaskBoundary+activeRegionfromuseMapHierarchy - Ensure
maskOpacityis1(default)
Union boundary not found
- First union click loads ~12 MB ADM4 data (may take a few seconds)
- Union names must match bundled location data spelling
Map shows wrong region after fast clicking
useMapHierarchyhandles race conditions internally; wait forisLoadingBoundaryto becomefalse
Development
Build from source
git clone https://github.com/sadikworkbd5622/Bd-map.git
cd Bd-map/packages/bd-map-explorer
npm install
npm run buildLink locally into another project
cd packages/bd-map-explorer
npm link
cd your-react-app
npm link bd-map-explorerPublish new version
# Bump version in package.json first (e.g. 1.0.1)
npm run build
npm publish --access publicLicense
MIT © sadikworkbd5622
Links
- npm: https://www.npmjs.com/package/bd-map-explorer
- GitHub: https://github.com/sadikworkbd5622/Bd-map
- Issues: https://github.com/sadikworkbd5622/Bd-map/issues
