This tool converts Geonames data (allCountries.zip and alternateNamesV2.zip) into a clean, structured SQLite database with a proper administrative hierarchy.
- Reads both datasets directly from ZIP archives (no extraction required)
- Stores core geospatial entities in a
placestable - Stores all alternative names in a separate
namestable - Generates a valid hierarchical parent→child structure using administrative levels (ADM1–ADM4)
- Supports recursive tree queries (SQLite
WITH RECURSIVE) - Suitable for geographic apps, routing systems, map backends, knowledge graphs, entity linking and place normalization
| Field | Type | Description |
|---|---|---|
| geonameid | INTEGER | Primary ID from Geonames |
| parent_id | INTEGER | Parent geonameid (or NULL for root) |
| name | TEXT | Default English name |
| feature_code | TEXT | Geonames feature classification |
| country_code | TEXT | Two-letter ISO country code |
| admin1 | TEXT | First-level admin code |
| admin2 | TEXT | Second-level admin code |
| admin3 | TEXT | Third-level admin code |
| admin4 | TEXT | Fourth-level admin code |
| lat | REAL | Latitude |
| lon | REAL | Longitude |
| Field | Type | Description |
|---|---|---|
| geonameid | INTEGER | FK to places |
| lang | TEXT | Language or ISO code |
| name | TEXT | Alternative name |
Hierarchy is created based on:
feature_codecountry_codeadmin1–admin4
Processing:
PCLI→ Countries → no parentADM1→ belongs to a countryADM2→ belongs to ADM1ADM3→ belongs to ADM2ADM4→ belongs to ADM3PPL*→ assigned to nearest ADM3
Result:
Germany → Nordrhein-Westfalen → Kreis → municipality → town
Place files in:
data/allCountries.zipdata/alternateNamesV2.zip
Run import:
python3 convert.pyThis creates:
data/geonames.sqlite3
WITH RECURSIVE r(geonameid, parent_id, name, level) AS (
SELECT geonameid, parent_id, name, 0 FROM places WHERE name='Germany'
UNION ALL
SELECT p.geonameid, p.parent_id, p.name, r.level + 1
FROM places p
JOIN r ON p.parent_id = r.geonameid
)
SELECT * FROM r ORDER BY level, name;Example: Retrieve cities under a specific ADM3
WITH RECURSIVE r(id) AS (
SELECT geonameid FROM places WHERE geonameid=XXXXXX
UNION ALL
SELECT geonameid FROM places WHERE parent_id IN r
)
SELECT * FROM places
WHERE geonameid IN r AND feature_code LIKE 'PPL%';- Initial import is large (10–20M rows)
- SQLite handles it, but consider migrating later to:
- Postgres
- DuckDB
- ClickHouse
- Population data integration
- Time zones
- Elevation
- OSM polygon cross-linking
- Entity deduplication and normalization
Use freely. Data provided under Geonames open license: http://www.geonames.org/export/
This import tool is optimized for real-world geospatial systems and knowledge-graph applications. If you use it, consider attribution, but it’s optional. Contributions welcome.