Metadata-Version: 2.4
Name: psgc
Version: 2026.4.13.0
Summary: Philippine Standard Geographic Code (PSGC) with coordinates, spatial queries, and fuzzy search. Community-maintained, not affiliated with PSA.
Project-URL: Homepage, https://github.com/fish-and-bear/psgc
Project-URL: Demo, https://psgc-explorer-production.up.railway.app/
Project-URL: Repository, https://github.com/fish-and-bear/psgc
Project-URL: Changelog, https://github.com/fish-and-bear/psgc/blob/main/CHANGELOG.md
Project-URL: Bug Tracker, https://github.com/fish-and-bear/psgc/issues
Author: psgc contributors
License-Expression: MIT
License-File: LICENSE
Keywords: barangay,geocoding,geography,geospatial,philippines,psgc
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: rapidfuzz>=3.0
Provides-Extra: all
Requires-Dist: click>=8.0; extra == 'all'
Requires-Dist: pyyaml>=6.0; extra == 'all'
Requires-Dist: scipy>=1.10; extra == 'all'
Provides-Extra: cli
Requires-Dist: click>=8.0; extra == 'cli'
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: click>=8.0; extra == 'dev'
Requires-Dist: fiona>=1.9; extra == 'dev'
Requires-Dist: geopandas>=0.14; extra == 'dev'
Requires-Dist: hatchling>=1.25; extra == 'dev'
Requires-Dist: openpyxl>=3.1; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: scipy>=1.10; extra == 'dev'
Requires-Dist: shapely>=2.0; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Provides-Extra: geo
Requires-Dist: scipy>=1.10; extra == 'geo'
Provides-Extra: yaml
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
Description-Content-Type: text/markdown

# 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**.

**[Live Demo](https://psgc-explorer-production.up.railway.app/)**

---

## Quick Start

```bash
pip install psgc
```

```python
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

```bash
# 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](https://peps.python.org/pep-0440/) versions because the bundled PSGC data release is the main compatibility signal.

- Format: `YYYY.M.D.N`
- `YYYY.M.D` is the PSGC data release date, also exposed as `psgc.__data_date__`.
- `N` increments for package, parser, or data-correction releases against the same data date.
- Current package version: `2026.4.13.0`

Check the installed version with:

```python
import psgc

psgc.__version__    # '2026.4.13.0'
psgc.__data_date__  # '2026-04-13'
```

Release notes live in [CHANGELOG.md](CHANGELOG.md).
Maintainer release steps live in [RELEASING.md](RELEASING.md).

---

## Python API

### Look Up a Place

```python
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

```python
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

```python
results = psgc.suggest("mak", limit=5)
# [{"name": "Makati City", "psgc_code": "...", "level": "city"}, ...]
```

### Spatial Queries

Requires `pip install psgc[geo]`.

```python
# 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

```python
# 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

```python
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

```python
info = psgc.zip_lookup("1000")
info["area"]      # 'Ermita, Manila'
info["city"]      # 'City of Manila'
```

### PSGC Code Validation

```python
is_valid, reason = psgc.validate("1380608000")
# (True, "Valid city/municipality code")
```

### Export

```python
# 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

```python
# 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]`.

```bash
# 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](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](https://psa.gov.ph/classification/psgc). Public information under the Philippine Statistical Act of 2013 (RA 10625).
- **Administrative boundary coordinates and area**: [OCHA HDX Philippines Subnational Administrative Boundaries](https://data.humdata.org/dataset/cod-ab-phl), sourced from PSA and NAMRIA. Licensed under [CC BY-IGO](https://creativecommons.org/licenses/by/3.0/igo/).
- **ZIP code supplement**: Community ZIP-code data from [simonpangan/Philippines-Zip-Codes](https://github.com/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

[MIT](LICENSE)
