Philippine Standard Geographic Code (PSGC) with coordinates, spatial queries, and fuzzy search. Community-maintained, not affiliated with PSA.
Project description
psgc
Philippine Standard Geographic Code (PSGC) Python package with latitude/longitude coordinates, spatial queries, fuzzy search, reverse geocoding, address parsing, and GeoJSON export.
42,010 barangays, 1,656 cities/municipalities/sub-municipalities, 82 official provinces, 20 package parent groups, and 18 regions with 2024 Census population data. Based on the official PSA PSGC Q1 2026 release.
Quick Start
pip install psgc
import psgc
place = psgc.get("Ermita")
place.coordinate # Coordinate(14.5838, 120.9823)
place.coordinate_source # 'child_area_weighted_centroid'
place.parent # Province('National Capital Region (NCR)', ...)
place.breadcrumb # ['National Capital Region (NCR)', 'Ermita']
Requirements: Python 3.10+
Features
| Feature | Description |
|---|---|
| Coordinates | Lat/lng with coordinate_source provenance for all ~42,000 barangays, cities, provinces, and regions |
get() |
Look up any place by name or PSGC code |
| Fuzzy Search | RapidFuzz-based matching with Filipino phonetic rules |
| Spatial Queries | Nearest barangay, radius search, distance by place name |
| Reverse Geocoding | Coordinates to barangay (point-in-polygon + centroid fallback) |
| Address Parsing | Parse unstructured Filipino addresses into components |
| Autocomplete | Prefix trie for sub-millisecond suggestions |
| GeoJSON Export | Export points and boundaries as GeoJSON FeatureCollections |
| Island Groups | Every unit tagged Luzon / Visayas / Mindanao |
| ZIP Codes | Supplemental ZIP code to location mapping |
| Hierarchical Navigation | .parent, .children, .siblings, .breadcrumb |
| Population Density | Computed from population and polygon area |
| CLI | Full command-line interface for all features |
| Lightweight | 1 core dependency (rapidfuzz). stdlib dataclasses, no pydantic. |
| Typed | PEP 561 py.typed, full IDE autocomplete support |
Installation
# Core package (search, data, address parsing)
pip install psgc
# With spatial queries (nearest, radius, reverse geocode)
pip install psgc[geo]
# With CLI
pip install psgc[cli]
# Everything
pip install psgc[all]
Optional Extras
| Extra | Adds | Use Case |
|---|---|---|
[geo] |
scipy |
Spatial index, nearest/radius queries |
[cli] |
click |
Command-line interface |
[yaml] |
pyyaml |
YAML export format |
[all] |
All of the above | Full install |
Versioning
psgc uses date-based PEP 440 versions because the bundled PSGC data release is the main compatibility signal.
- Format:
YYYY.M.D.N YYYY.M.Dis the PSGC data release date, also exposed aspsgc.__data_date__.Nincrements for package, parser, or data-correction releases against the same data date.- Current package version:
2026.4.13.0
Check the installed version with:
import psgc
psgc.__version__ # '2026.4.13.0'
psgc.__data_date__ # '2026-04-13'
Release notes live in CHANGELOG.md. Maintainer release steps live in RELEASING.md.
Python API
Look Up a Place
import psgc
# By name (fuzzy matched)
place = psgc.get("Ermita")
place.name # 'Ermita'
place.coordinate # Coordinate(14.5838, 120.9823)
place.parent # Province('National Capital Region (NCR)', ...)
place.breadcrumb # ['National Capital Region (NCR)', 'Ermita']
place.coordinate_source # 'child_area_weighted_centroid'
# By PSGC code (instant O(1) lookup)
place = psgc.get("1380608000")
# Check if a place exists
psgc.exists("Ermita") # True
psgc.exists("Xanadu") # False
# Ambiguous names raise with helpful guidance
try:
psgc.get("Barangay 1 (Poblacion)")
except psgc.AmbiguousLookupError as e:
print(e.matches) # list of matching places to choose from
# Disambiguate:
psgc.get("Barangay 1 (Poblacion), Legazpi City")
Fuzzy Search
results = psgc.search("Cebu")
results[0].name # 'Cebu'
results[0].score # 100.0
results[0].level # 'province'
results[0].place # Province('Cebu', ...) -- the actual object
# From the result, navigate the hierarchy
results[0].place.children # list of cities in Cebu
# Custom search with hooks and threshold
results = psgc.search(
"Sebu",
n=5,
match_hooks=["city"], # search cities only
threshold=70.0,
phonetic=True, # Filipino phonetic matching
)
Autocomplete
results = psgc.suggest("mak", limit=5)
# [{"name": "Makati City", "psgc_code": "...", "level": "city"}, ...]
Spatial Queries
Requires pip install psgc[geo].
# Nearest barangays to a GPS point
results = psgc.nearest(14.5995, 120.9842, n=5)
results[0].place # Barangay('Barangay 394', ...)
results[0].distance_km # 0.108
# All barangays within 5 km
results = psgc.within_radius(14.5995, 120.9842, radius_km=5)
# Straight-line distance between two places (not driving distance)
psgc.distance("Ermita, Manila", "Cebu City") # 562.266 km (as the crow flies)
# Reverse geocode: coordinates -> barangay
result = psgc.reverse_geocode(14.5547, 121.0244)
result.barangay # 'Poblacion'
result.city # 'Makati City'
result.province # 'NCR, Third District'
result.place # Barangay('Poblacion', ...) -- full object
Data Access
# All 18 regions
for r in psgc.regions:
print(f"{r.name} ({r.island_group.value})")
print(f" Lat: {r.coordinate.latitude}, Lng: {r.coordinate.longitude}")
# Hierarchical navigation
brgy = psgc.barangays[0]
brgy.parent # parent City
brgy.parent.parent # parent Province
brgy.parent.parent.parent # parent Region
brgy.siblings[:3] # other barangays in same city
brgy.is_urban # True/False
# Cities with sub-municipalities (e.g. Manila)
manila = psgc.get("Manila")
manila.children # 897 barangays (walks through sub-municipalities)
manila.sub_municipalities # [Tondo I/II, Binondo, Sampaloc, ...]
# Flat denormalized list (ideal for filtering)
urban_ncr = [b for b in psgc.flat if b.region_name and "NCR" in b.region_name and b.urban_rural == "U"]
# Recursive tree
for region_node in psgc.tree:
for province_node in region_node.components:
print(f" {province_node.name}: {len(province_node.components)} cities")
Address Parsing
result = psgc.parse_address("123 Rizal St., Brgy. San Antonio, Makati City")
result.street # '123 Rizal St.'
result.barangay # 'San Antonio'
result.city # 'Makati City'
result.province # 'NCR, Third District'
result.confidence # 0.95
ZIP Code Lookup
info = psgc.zip_lookup("1000")
info["area"] # 'Ermita, Manila'
info["city"] # 'City of Manila'
PSGC Code Validation
is_valid, reason = psgc.validate("1380608000")
# (True, "Valid city/municipality code")
Export
# GeoJSON with coordinates
geojson = psgc.to_geojson(level="barangay", region="NCR", as_dict=True)
# CSV with lat/lng columns
psgc.to_csv(level="barangay", output="barangays.csv")
Logging
# Silent by default. Enable for debugging:
# Enable verbose output:
psgc.setup_logging(verbose=True)
# Or via environment variable:
# PSGC_VERBOSE=true python my_script.py
CLI
Requires pip install psgc[cli].
# Look up
psgc search "Cebu" -n 3
psgc suggest "makat"
# Spatial (requires psgc[geo])
psgc nearest 14.5995 120.9842 -n 5
psgc within-radius 14.5995 120.9842 --km 10
psgc reverse-geocode 14.5833 120.9822
psgc distance "Ermita, Manila" "Cebu City"
# Address parsing
psgc parse "Brgy. San Antonio, Makati City"
# Export
psgc export --format geojson --level barangay --region "NCR" -o ncr.geojson
psgc export --format csv --level city -o cities.csv
# Info
psgc info stats
psgc info version
psgc validate 1380608000
psgc zip 1000
Data Sources
| Data | Source | Coverage |
|---|---|---|
| Names, codes, hierarchy | PSA PSGC Q1 2026 release; Q4 2025 Publication Datafile as base | All 42,010 current barangays |
| Population | 2024 Census (via PSGC masterlist) | 100% of current barangays |
| Urban/Rural | PSGC masterlist | 100% |
| Income classification | PSGC masterlist | 99% of cities |
| Coordinates | HDX/OCHA Nov 2023 shapefiles plus PSA correspondence-code/name-parent matching | 99.8% HDX-derived (41,926 barangays), 84 fallback |
| Area (km2) | HDX/OCHA Nov 2023 shapefiles | 99.8% (41,926 barangays) |
| ZIP codes | Supplemental open ZIP dataset plus legacy curated entries | 1,783 ZIP codes |
Coordinates: Every record with coordinates also has a coordinate_source value. Barangays are classified as:
hdx_exact_2023: current PSGC code matched a November 2023 HDX/NAMRIA polygon.hdx_correspondence_2023: current PSGC code was matched to an HDX polygon using the PSA correspondence code.hdx_name_parent_2023: current PSGC code was matched to a unique HDX polygon by barangay name within the same parent city/municipality.merged_hdx_area_weighted_2026q1: Q1 2026 merge applied from multiple HDX polygons.fallback_unverified: no HDX polygon match is available; coordinates are retained only as low-confidence display points.
Parent city, province, package-group, and region coordinates are recomputed from child barangay coordinates using area-weighted centroids. Package parent groups are used for NCR, highly urbanized cities, and PSA special groupings so independent cities are no longer attached to unrelated provinces. See CHANGELOG.md for release-specific changes.
Performance
| Operation | Time (42K barangays) |
|---|---|
import psgc |
< 1ms (lazy loading) |
get("1380100000") |
~0.001ms (code lookup) |
get("Taguig") |
~100ms (fuzzy search) |
search("Manila") |
~100ms |
suggest("mak") |
~0.5ms |
validate(code) |
~0.001ms |
nearest(lat, lng) |
< 1ms (after first call builds index) |
Known Limitations
| What | Limitation | Details |
|---|---|---|
| Coordinates | 84 barangays remain low-confidence fallback points | 41,926 barangays are HDX-derived or HDX-merged. Check coordinate_source before research or distance work. |
| Distance | Straight-line (Haversine), not driving/walking | Use a routing API (OSRM, Google Maps) for road distance |
| Reverse geocode | Uses nearest centroid unless boundaries are supplied separately | Good for approximate lookup, not a substitute for polygon containment in formal GIS workflows |
| ZIP codes | Supplemental, not a PSA field | ZIP data is not part of PSGC and should be verified against PHLPost for mailing-critical uses |
| Area (km2) | Available for 41,926 barangays | population_density works only where area_km2 is present |
| City names | PSA uses "City of X" format | get("Makati") auto-resolves to "City of Makati" for major cities. For obscure cities, use the full PSA name or PSGC code. |
Disclaimer
This is a community-maintained open-source project. It is not affiliated with, endorsed by, or officially connected to the Philippine Statistics Authority (PSA) or NAMRIA.
Data Attribution
- PSGC data (names, codes, population, classifications): Philippine Statistics Authority. Public information under the Philippine Statistical Act of 2013 (RA 10625).
- Administrative boundary coordinates and area: OCHA HDX Philippines Subnational Administrative Boundaries, sourced from PSA and NAMRIA. Licensed under CC BY-IGO.
- ZIP code supplement: Community ZIP-code data from simonpangan/Philippines-Zip-Codes, with existing curated package entries preserved for backwards-compatible display strings. ZIP codes are not part of the official PSGC.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file psgc-2026.4.13.0.tar.gz.
File metadata
- Download URL: psgc-2026.4.13.0.tar.gz
- Upload date:
- Size: 1.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5d4a9b5dd0b3a35260a57e167d1bc8577693c0d3d65ace1c2db488e7c535958
|
|
| MD5 |
fcd30fa9ed8d41db36a48d8de0691716
|
|
| BLAKE2b-256 |
483de3c5f5ed299786119ea0e64cab697aa6bd2006df6fdbef7df0cb17c5e4d2
|
Provenance
The following attestation bundles were made for psgc-2026.4.13.0.tar.gz:
Publisher:
publish.yaml on fish-and-bear/psgc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
psgc-2026.4.13.0.tar.gz -
Subject digest:
c5d4a9b5dd0b3a35260a57e167d1bc8577693c0d3d65ace1c2db488e7c535958 - Sigstore transparency entry: 1675266639
- Sigstore integration time:
-
Permalink:
fish-and-bear/psgc@baa26c148efc788fdf8d8414d0c125151c2b09a5 -
Branch / Tag:
refs/tags/v2026.4.13.0 - Owner: https://github.com/fish-and-bear
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@baa26c148efc788fdf8d8414d0c125151c2b09a5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file psgc-2026.4.13.0-py3-none-any.whl.
File metadata
- Download URL: psgc-2026.4.13.0-py3-none-any.whl
- Upload date:
- Size: 1.5 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9662b69f3313d90089896c567d9b82be2452358d16f3eb561a9f44b5212a778
|
|
| MD5 |
1cd4f6564cb8eba8a32ca7a3cef43cdb
|
|
| BLAKE2b-256 |
a6a9f2a14efb788e536dc0bf76df3b9d69f86a2da382175d02bb9615ad0f6516
|
Provenance
The following attestation bundles were made for psgc-2026.4.13.0-py3-none-any.whl:
Publisher:
publish.yaml on fish-and-bear/psgc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
psgc-2026.4.13.0-py3-none-any.whl -
Subject digest:
e9662b69f3313d90089896c567d9b82be2452358d16f3eb561a9f44b5212a778 - Sigstore transparency entry: 1675266693
- Sigstore integration time:
-
Permalink:
fish-and-bear/psgc@baa26c148efc788fdf8d8414d0c125151c2b09a5 -
Branch / Tag:
refs/tags/v2026.4.13.0 - Owner: https://github.com/fish-and-bear
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@baa26c148efc788fdf8d8414d0c125151c2b09a5 -
Trigger Event:
push
-
Statement type: