Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Geonames Laravel Package

yurtesen/geonames

Laravel package for importing GeoNames geographical data (countries, regions, cities) into your database, complete with migrations, Eloquent models, and Artisan commands for fast, searchable location data in your app.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Check compatibility before anything else. The last release was September 2020, so expect Laravel 6/7-era conventions ($dates, no typed properties, no return types on relations). On Laravel 9+ you will likely need composer require yurtesen/geonames --with-all-dependencies plus a fork or patch for $casts/Illuminate\Support\Facades changes. Read composer.json in the repo first — it is the fastest way to know whether you are in for a five-minute install or an afternoon of patching.

2. Install and publish:

composer require yurtesen/geonames
php artisan vendor:publish --provider="Yurtesen\Geonames\GeonamesServiceProvider"
php artisan migrate

Publish both config and migrations — the config holds the filesystem path where the package expects the geonames.org dumps to live, and the migrations create the geonames, alternate_names, admin1_codes, admin2_codes, hierarchy, etc. tables.

3. Supply the raw data yourself. The package is an importer, not a downloader. Grab the dumps from geonames.org (allCountries.zip, alternateNamesV2.zip, admin1CodesASCII.txt, admin2Codes.txt, hierarchy.zip, countryInfo.txt) and unzip them into the directory named in config/geonames.php. Budget disk: unzipped allCountries is roughly 1.5 GB and alternate names another ~1 GB. Do this on a machine with room, not on a container with a 2 GB volume.

4. Run the import from the CLI, then confirm with php artisan list | grep -i geonames — command names have shifted across releases, so trust the CLI over any blog post. Start with a single country if the config supports scoping; a full import is tens of minutes to hours.

First real use case: a country → region → city cascade in a signup or address form. Query the GeoName model filtered on country_code and feature_code = 'P' for populated places, and admin1_codes for the region dropdown. That alone justifies the package — you get ~4M places with lat/lng, elevation, population, and timezone for free.

Implementation Patterns

Anchor your domain data to geoname_id. Add a nullable geoname_id foreign key (or plain indexed column) to your addresses/locations table rather than copying name strings. You get timezone, coordinates, and population for free, and a stable integer ID that survives geonames renames.

Scope the import by feature class. Importing all 4M+ rows and all alternate names is rarely what you want. Most apps need feature_class = 'P' (populated places) plus 'A' (admin divisions) and only the languages they actually display. Filtering alternate names by isolanguage (en, de, …) cuts the noisiest table by an order of magnitude and makes name search usable.

Model the hierarchy through admin1/admin2 codes, not the hierarchy table. For 99% of address forms the country_code.admin1_code.admin2_code prefix is enough and it is indexed. Reach for hierarchy.zip only if you need parent-chain lookups (e.g. "all places within 50 km of this admin3").

Cache the small tables. Countries and admin1 divisions are read on nearly every page and change monthly at most. Cache the full list in Redis/file cache with a long TTL and bust it from your post-import job. Never issue an uncached GeoName::where('feature_code','P')->get() in a request — that is a multi-million-row hydration.

Search with prefix, not LIKE %…%. For a city autocomplete, do where('ascii_name', 'like', $q.'%') against a B-tree index, ordered by population desc, and cap to 10–20 rows. Leading-wildcard search will table-scan and time out. If you need fuzzy matching, add a MySQL fulltext index on name/ascii_name post-import or push search to Meilisearch/Scout.

Schedule the refresh as a background job. Wrap the update command in a queued job or Schedule::command(...)->withoutOverlapping()->onOneServer(), writing to a staging table and swapping when done if you cannot afford a partially-updated table during business hours.

Import ordering matters. Run the reference tables (country info, admin codes) first, then places, then alternate names — the FK and update passes depend on that order, and alternate names resolve against existing geoname_ids. Follow the order in the README rather than inventing your own.

Gotchas and Tips

The GPL-3.0 license is the big one. This is a copyleft license, not MIT. If you ship it as part of a closed-source SaaS or a distributed product, get legal review — the common reading is that linking this into your app obliges you to release source under GPL. For many commercial projects the correct move is to reimplement the (simple) import logic from the public geonames dumps yourself, which avoids the dependency entirely.

Separately, the data is CC-BY 4.0. Even if you drop the package, geonames requires attribution ("Data from geonames.org"). Put it in your footer or an About page.

Memory is the failure mode. Set memory_limit = -1 or at least 2G in the CLI context, and call DB::disableQueryLog() before the import — Eloquent's query log will happily eat gigabytes over millions of inserts. If the import dies mid-way, the package's chunking should let you resume, but verify the command is idempotent before relying on that.

Never run the import through the web. It will hit max_execution_time and leave you with a half-populated table. Use nohup php artisan ... &, a queue worker, or a Forge/envoy task with a generous timeout.

Indexes after, not during. Bulk inserts are dramatically faster into a table with minimal indexes; add the search indexes (and any fulltext) once the import completes. If migrations already define indexes, consider dropping them around the import run.

Feature codes are a UX minefield. feature_class = 'P' includes abandoned and historical places; filter on feature_code in ('PPL','PPLA','PPLA2','PPLC', …) and prefer rows with non-null population for suggestion lists. Otherwise your autocomplete offers users villages of 3 people alongside capitals.

Alternate names are historical and multi-script. Rows include exonyms, abbreviations, and links (isolanguage = 'link'). Filter on is_preferred_name and a whitelist of isolanguage codes, or you will surface "Konstantinopel" for Istanbul depending on locale.

Nulls are normal. Small places lack admin2_code, timezone, elevation, and population. Make those columns nullable in your own schema and guard in code — do not assume a timezone exists.

"Update" may not delete. GeoNames removes/merges a few thousand records per release. Verify whether the package's update path handles deletions; if not, schedule a full periodic re-import (into a staging table) rather than trusting incrementals forever.

Expect to maintain it yourself. With one release in 2020 and ~31 stars, there is no active upstream. Pin the version, fork it internally if you must patch, and write a smoke test that imports one country and asserts a known city resolves — you will need it the day you upgrade Laravel.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package