psgc-areas
Version:
Provides the full Philippine Standard Geographic Code (PSGC) dataset in JSON with multiple formats (hierarchy, flat list of all areas, regions, provinces, cities, municipalities, municipal districts, and barangays).
448 lines (421 loc) • 13.7 kB
JavaScript
import ExcelJS from "exceljs";
// Column indices of PSGC data (1-based indexing).
export const COL_INDICES = Object.freeze({
CODE: 1,
LEGACY_CODE: 3,
NAME: 2,
OLD_NAME: 5,
GEOGRAPHIC_LEVEL: 4,
CITY_CLASS: 6,
INCOME_CLASSIFICATION: 7,
URBAN_OR_RURAL: 8,
POPULATION: 9,
STATUS: 11,
POPULATION_NOTE: 10,
});
// Mapping of geographic levels.
export const GEOGRAPHIC_LEVELS = Object.freeze({
REGION: "Reg",
PROVINCE: "Prov",
MUNICIPALITY: "Mun",
CITY: "City",
MUNICIPAL_DISTRICT: "SubMun",
BARANGAY: "Bgy",
});
// Mapping of city classes.
export const CITY_CLASSES = Object.freeze({
HIGHLY_URBANIZED_CITY: "HUC",
IND_COMPONENT_CITY: "ICC",
COMPONENT_CITY: "CC",
});
export const VALUE_UNSPECIFIED = "-";
// Mapping of Urban/Rural.
export const URBAN_OR_RURAL = Object.freeze({
URBAN: "U",
RURAL: "R",
UNSPECIFIED: VALUE_UNSPECIFIED,
});
/**
* Logs a warning to console about an unknown value.
* @param {String} aWhat
* @param {Number} [aRowIndex]
*/
function _warnUnknown(aWhat, aRowIndex) {
if (aRowIndex == null) {
console.warn(`Found unknown ${aWhat}.`);
return;
}
console.warn(`Found unknown ${aWhat} at row ${aRowIndex}`);
}
/**
* Retrieves the friendly name of an abbreviated geographic level value.
* @param {String} aGeographicLevel the raw geographic level value.
* @returns {String} the friendly name.
*/
export function getGeographicLevelName(aGeographicLevel) {
switch (aGeographicLevel) {
case GEOGRAPHIC_LEVELS.REGION:
return "Region";
case GEOGRAPHIC_LEVELS.PROVINCE:
return "Province";
case GEOGRAPHIC_LEVELS.MUNICIPALITY:
return "Municipality";
case GEOGRAPHIC_LEVELS.CITY:
return "City";
case GEOGRAPHIC_LEVELS.MUNICIPAL_DISTRICT:
return "Municipal District";
case GEOGRAPHIC_LEVELS.BARANGAY:
return "Barangay";
default:
return null;
}
}
/**
* Retrieves the friendly name of an abbreviated city class value.
* @param {String} aCityClass the raw city class value.
* @returns {String} the friendly name.
*/
export function getCityClassName(aCityClass) {
switch (aCityClass) {
case CITY_CLASSES.HIGHLY_URBANIZED_CITY:
return "Highly Urbanized City";
case CITY_CLASSES.IND_COMPONENT_CITY:
return "Independent Component City";
case CITY_CLASSES.COMPONENT_CITY:
return "Component City";
default:
return null;
}
}
/**
* Returns the parsed area from a row.
* @param {ExcelJS.Row} aRow the ExcelJS row containing data.
* @param {Number} aRowIndex the one-based index of the row.
*/
export function PSGCArea(aRow, aRowIndex) {
let code, legacyCode, name, oldName, geographicLevel, cityClass,
incomeClassification, isUrban, isRural, isUnspecified, population,
status, populationNote;
aRow.eachCell(function (aCell, aColIndex) {
let value = aCell.value;
switch (aColIndex) {
case COL_INDICES.CODE:
code = value.toString().trim();
break;
case COL_INDICES.LEGACY_CODE:
legacyCode = value.toString().trim();
break;
case COL_INDICES.NAME:
name = value.toString().trim();
break;
case COL_INDICES.OLD_NAME:
oldName = value.toString().trim();
break;
case COL_INDICES.GEOGRAPHIC_LEVEL:
geographicLevel = value.toString().trim();
break;
case COL_INDICES.CITY_CLASS:
cityClass = value.toString().trim();
break;
case COL_INDICES.INCOME_CLASSIFICATION:
if (value == VALUE_UNSPECIFIED) {
break;
}
incomeClassification = value.toString().trim();
break;
case COL_INDICES.URBAN_OR_RURAL:
if (value == "") {
// Ignore empty values.
break;
}
isUrban = (value == URBAN_OR_RURAL.URBAN);
isRural = (value == URBAN_OR_RURAL.RURAL);
isUnspecified = (value == URBAN_OR_RURAL.UNSPECIFIED);
if (!isUrban && !isRural && !isUnspecified) {
_warnUnknown("urban/rural value", aRowIndex);
}
break;
case COL_INDICES.POPULATION:
population = value;
if (population.formula) {
population = value.result;
}
break;
case COL_INDICES.STATUS:
status = value.toString().trim();
break;
case COL_INDICES.POPULATION_NOTE:
populationNote = value.toString().trim();
break;
default:
// Ignore unknown columns.
break;
}
});
// XXX: Special cases for areas with no geographic level value in
// the PSGC publication.
if (!geographicLevel) {
// Treat BARMM Special Geographic Area as a province.
if (code === "1999900000") {
name += " (Ligawasan)";
geographicLevel = GEOGRAPHIC_LEVELS.PROVINCE;
}
// Treat City of Isabela (Not a Province) as a province.
if (code === "0990100000" ||
code === "099700000" ||
legacyCode === "099700000") {
geographicLevel = GEOGRAPHIC_LEVELS.PROVINCE;
}
}
if (code === undefined) {
_warnUnknown("PSGC code", aRowIndex);
}
/** @type {String} */
this.code = code;
if (legacyCode) {
/** @type {(String|undefined)} */
this.legacyCode = legacyCode;
}
if (name) {
/** @type {String} */
this.name = name;
}
if (oldName) {
/** @type {(String|undefined)} */
this.oldName = oldName;
}
if (geographicLevel) {
/** @type {(String|undefined)} */
this.geographicLevel = geographicLevel;
}
if (cityClass) {
/** @type {(String|undefined)} */
this.cityClass = cityClass;
}
if (incomeClassification) {
/** @type {(String|undefined)} */
this.incomeClassification = incomeClassification;
}
if (isUnspecified !== undefined) {
/** @type {(Boolean|undefined)} */
this.isUrban = isUrban;
/** @type {(Boolean|undefined)} */
this.isRural = isRural;
}
if (population !== undefined) {
/** @type {Number} */
this.population = population;
}
if (status) {
/** @type {(String|undefined)} */
this.status = status;
}
if (populationNote) {
/** @type {(String|undefined)} */
this.populationNote = populationNote;
}
}
/**
* Retrieves whether a region has provinces.
* @param {PSGCArea} aArea the PSGC area to evaluate.
* @returns {Boolean}
*/
function _hasProvincesFor(aArea) {
const PSGC_NCR = "1300000000";
const PSGC_NCR_LEGACY = "130000000";
return !(
aArea.code == PSGC_NCR ||
aArea.code == PSGC_NCR_LEGACY ||
aArea.legacyCode == PSGC_NCR_LEGACY
);
}
/**
* Retrieves whether a city has municipal districts.
* @param {PSGCArea} aArea the PSGC area to evaluate.
* @returns {Boolean}
*/
function _hasMunicipalDistrictsFor(aArea) {
const PSGC_MANILA = "1380600000";
const PSGC_MANILA_LEGACY = "133900000";
return (
aArea.code == PSGC_MANILA ||
aArea.code == PSGC_MANILA_LEGACY ||
aArea.legacyCode == PSGC_MANILA_LEGACY
);
}
/**
* Returns a PSGC dataset from a worksheet.
* @param {ExcelJS.Worksheet} aWorksheet an ExcelJS worksheet.
*/
export function PSGCDataset(aWorksheet) {
this.all = [];
this.areas = [];
this.regions = [];
this.provinces = [];
this.cities = [];
this.municipalities = [];
this.municipalDistricts = [];
this.barangays = [];
let region = null;
let regionHasProvinces = null;
let province = null;
let municipality = null;
let city = null;
let cityHasMunicipalDistricts = null;
let municipalDistrict = null;
const dataset = this;
aWorksheet.eachRow(function(aRow, aRowIndex) {
// Skip header row.
if (aRowIndex == 1) {
return;
}
let area = new PSGCArea(aRow, aRowIndex);
dataset.areas.push(structuredClone(area));
if (area.geographicLevel === undefined) {
console.log(`Ignoring area ${area.code} (${area.name}) with `
+ "no geographic level");
return;
}
let geographicLevel = area.geographicLevel;
delete area.geographicLevel;
switch (geographicLevel) {
case GEOGRAPHIC_LEVELS.REGION: {
dataset.regions.push(structuredClone(area));
region = area;
regionHasProvinces = _hasProvincesFor(region);
if (regionHasProvinces) {
region.provinces = [];
} else {
region.municipalities = [];
region.cities = [];
}
dataset.all.push(region);
province = null;
municipality = null;
city = null;
cityHasMunicipalDistricts = null;
municipalDistrict = null;
break;
}
case GEOGRAPHIC_LEVELS.PROVINCE: {
let clone = structuredClone(area);
clone.region = region.code;
dataset.provinces.push(clone);
municipality = null;
city = null;
cityHasMunicipalDistricts = null;
municipalDistrict = null;
province = area;
province.municipalities = [];
province.cities = [];
region.provinces.push(province);
break;
}
case GEOGRAPHIC_LEVELS.MUNICIPALITY: {
let clone = structuredClone(area);
clone.region = region.code;
if (regionHasProvinces) {
clone.province = province.code;
}
dataset.municipalities.push(clone);
city = null;
cityHasMunicipalDistricts = null;
municipalDistrict = null;
municipality = area;
municipality.barangays = [];
if (regionHasProvinces) {
province.municipalities.push(municipality);
} else {
region.municipalities.push(municipality);
}
break;
}
case GEOGRAPHIC_LEVELS.CITY: {
let clone = structuredClone(area);
clone.region = region.code;
if (regionHasProvinces) {
clone.province = province.code;
}
dataset.cities.push(clone);
city = area;
cityHasMunicipalDistricts = _hasMunicipalDistrictsFor(city);
municipalDistrict = null;
municipality = null;
if (cityHasMunicipalDistricts) {
city.municipalDistricts = [];
} else {
city.barangays = [];
}
if (regionHasProvinces) {
province.cities.push(city);
} else {
region.cities.push(city);
}
break;
}
case GEOGRAPHIC_LEVELS.MUNICIPAL_DISTRICT: {
if (!city || !cityHasMunicipalDistricts) {
console.warn("Found stray municipal district at row "
+ `${aRowIndex}.`);
}
let clone = structuredClone(area);
clone.region = region.code;
clone.city = city.code;
dataset.municipalDistricts.push(clone);
municipalDistrict = area;
municipalDistrict.barangays = [];
city.municipalDistricts.push(municipalDistrict);
break;
}
case GEOGRAPHIC_LEVELS.BARANGAY: {
let clone = structuredClone(area);
clone.region = region.code;
let target;
if (municipality) {
target = municipality;
if (province) {
clone.province = province.code;
}
clone.municipality = municipality.code;
}
if (city) {
target = city;
if (province) {
clone.province = province.code;
}
clone.city = city.code;
}
if (municipalDistrict) {
target = municipalDistrict;
if (province) {
clone.province = province.code;
}
if (!city) {
console.warn("Found municipal district with no parent "
+ `city at row ${aRowIndex}.`);
}
if (!cityHasMunicipalDistricts) {
console.warn("Found muncipal district under a city that "
+ `shouldn't have one at row ${aRowIndex}.`);
}
clone.city = city.code;
clone.municipalDistrict = municipalDistrict.code;
}
dataset.barangays.push(clone);
let barangay = area;
if (target.barangays) {
target.barangays.push(barangay);
} else {
console.warn("Found barangay with unknown target "
+ "(municipality, city, muncicipal district) "
+ `at row ${aRowIndex}.`);
}
break;
}
default:
console.warn("Ignoring area with unknown geographic level "
+ `at row ${aRowIndex}.`);
break;
}
});
}