{"id":15412228,"url":"https://github.com/robthree/ngeonames","last_synced_at":"2025-10-24T08:16:09.048Z","repository":{"id":17970344,"uuid":"20967325","full_name":"RobThree/NGeoNames","owner":"RobThree","description":"Inspired by https://github.com/AReallyGoodName/OfflineReverseGeocode","archived":false,"fork":false,"pushed_at":"2024-10-14T13:22:58.000Z","size":704,"stargazers_count":90,"open_issues_count":1,"forks_count":21,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-16T10:08:32.925Z","etag":null,"topics":["api","c-sharp","dotnet","geonames","geonames-cities","geonames-countries"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RobThree.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["RobThree"],"custom":["https://paypal.me/robiii"]}},"created_at":"2014-06-18T15:28:08.000Z","updated_at":"2025-04-16T13:14:45.000Z","dependencies_parsed_at":"2024-10-19T17:56:08.120Z","dependency_job_id":null,"html_url":"https://github.com/RobThree/NGeoNames","commit_stats":{"total_commits":100,"total_committers":2,"mean_commits":50.0,"dds":"0.020000000000000018","last_synced_commit":"6f1adc71782c77c1637408fc9671a1208057c283"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobThree%2FNGeoNames","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobThree%2FNGeoNames/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobThree%2FNGeoNames/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobThree%2FNGeoNames/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RobThree","download_url":"https://codeload.github.com/RobThree/NGeoNames/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254509478,"owners_count":22082892,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","c-sharp","dotnet","geonames","geonames-cities","geonames-countries"],"created_at":"2024-10-01T16:51:54.336Z","updated_at":"2025-10-24T08:16:04.029Z","avatar_url":"https://github.com/RobThree.png","language":"C#","funding_links":["https://github.com/sponsors/RobThree","https://paypal.me/robiii"],"categories":[],"sub_categories":[],"readme":"# ![Logo](https://raw.githubusercontent.com/RobThree/NGeoNames/master/icon.png) NGeoNames\n\nInspired by [OfflineReverseGeocode](https://github.com/AReallyGoodName/OfflineReverseGeocode) found at [this Reddit post](http://www.reddit.com/r/programming/comments/281msj/). You may also be interested in [GeoSharp](https://github.com/Necrolis/GeoSharp). Uses [KdTree](https://github.com/codeandcats/KdTree). Built and tested on .Net 4.5.\n\nThis library provides classes for downloading, reading and parsing, writing and composing [files from](http://download.geonames.org/export/dump/) [GeoNames.org](http://download.geonames.org/export/zip/) and provides (reverse) geocoding methods like `NearestNeighbourSearch()` and `RadialSearch()` on the downloaded dataset(s).\n\nThis library is available as [NuGet package](https://www.nuget.org/packages/NGeoNames/).\n\n## Basic usage / example / \"quick start\"\n\n```c#\nvar datadir = @\"D:\\test\\geo\\\";\n\n// Download file (optional; you can point a GeoFileReader to existing files ofcourse)\nvar downloader = GeoFileDownloader.CreateGeoFileDownloader();\ndownloader.DownloadFile(\"NL.zip\", datadir);    // Zipfile will be automatically extracted\n\n// Read NL.txt file to memory (NL = ISO3166-2:The Netherlands)\nvar nldata = GeoFileReader.ReadExtendedGeoNames(Path.Combine(datadir, \"NL.txt\")).ToArray();   \n// Note: we \"Materialize\" the file to memory by calling ToArray()\n\n// We're going to use Amsterdam as \"search-center\"\nvar amsterdam = nldata.Where(n =\u003e \n        n.Name.Equals(\"Amsterdam\", StringComparison.OrdinalIgnoreCase) \n        \u0026\u0026 n.FeatureCode.Equals(\"PPLC\")\n    ).First();\n\n// Initialize a reversegeocoder with our geo-items from The Netherlands\nvar reversegeocoder = new ReverseGeoCode\u003cExtendedGeoName\u003e(nldata);\n// Locate 250 geo-items near the center of Amsterdam\nvar results = reversegeocoder.RadialSearch(amsterdam, 250);  \n// Print the results\nforeach (var r in results) {\n    Console.WriteLine(\n        string.Format(\n            CultureInfo.InvariantCulture, \"{0}, {1} {2} ({3:F4}Km)\", \n            r.Latitude, r.Longitude, r.Name, r.DistanceTo(amsterdam) / 1000d\n        )\n    );\n}\n```\n\n## Overview\n\nThe library provides for the following main operations:\n\n1. [Downloading / retrieving data from geonames.org](#downloading) (Optional)\n2. [Reading / parsing geonames.org data](#parsing)\n3. [Utilizing geonames.org data](#utilizing)\n4. [Writing / composing geonames.org data](#composing)\n\nThe library consists mainly of parsers, composers and entities (in their respective namespaces) and a `GeoFileReader` and `GeoFileWriter` to read/parse and write/compose geonames.org compatible files, a `GeoFileDownloader` to retrieve files from geonames.org and a `ReverseGeoCode\u003cT\u003e` class to do the heavy lifting of the reverse geocoding itself.\n\nBecause some \"geoname files\" can be very large (like `allcountries.txt`) we have a `GeoName` entity which is a simplified version (and baseclass) of an `ExtendedGeoName`. The `GeoName` class contains a unique id which can be used to resolve the `ExtendedGeoName` easily for more information when required. It is, however, recommended to use `\u003ccountrycode\u003e.txt` (e.g. `GB.txt`) `cities15000.txt` or `cities1000.txt` for example to reduce the dataset to a smaller size, You can also compose your own custom datasets using the `GeoFileWriter` and composers.\n\nAlso worth noting is that the readers return an `IEnumerable\u003cSomeEntity\u003e`; make sure that you materialize these enumerables to a list, array or other datastructure (using `.ToList()`, `.ToArray()`, `.ToDictionary()` etc.) if you access it more than once to avoid file I/O to the underlying file each time you access the data.\n\n### \u003ca name=\"downloading\"\u003e\u003c/a\u003eDownloading / retrieving data from geonames.org (Optional)\n\nTo download files from geonames.org you can use the `GeoFileDownloader` class which is, in essence, a wrapper for a basic [`WebClient`](http://msdn.microsoft.com/en-us/library/system.net.webclient.aspx). The simplest form is:\n\n```c#\n// Downloads (and extracts) geoname data in NL.zip from geonames.org\nGeoFileDownloader.CreateGeoFileDownloader()\n    .DownloadFile(\"NL.zip\", @\"D:\\my\\geodata\\geo\");\n    \n// Downloads (and extracts) postalcode data in NL.zip from geonames.org\nGeoFileDownloader.CreatePostalcodeDownloader()\n    .DownloadFile(\"NL.zip\", @\"D:\\my\\geodata\\postalcode\");\n```\n\nYou can specify the BaseUri in the `GeoFileDownloader` constructor or just pass an absolute url to the `DownloadFile()` method if you want to use another location than the default `http://download.geonames.org/export/dump/`. The static 'factory methods'  `CreateGeoFileDownloader()` and `CreatePostalcodeDownloader` are the easiest way to create a `GeoFileDownloader`; these use the built-in values for the BaseUri. The `GeoFileDownloader` has properties to set a (HTTP) `CachePolicy`, `Proxy` and `Credentials` to use when downloading the file. The filedownloader, by default, downloads a file only if the destination file doesn't exist *or* when the destination file has \"expired\" (by default 24 hours). It uses the file's CreationDate to determine when the file was downloaded and if a newer version should be downloaded. The \"TTL\", how long a file will be 'valid', can be set using the `DefaultTTL` property of the `GeoFileDownloader` class. You can also use the `DownloadFileWhenOlderThan()` method which allows you to explicitly set a TTL. When a filename is specified (e.g. `d:\\folder\\foo.txt`) the file will be named accordingly.\n\nZIP files are automatically extracted in the destinationfolder; the original zipfile is preserved because the `GeoFileDownloader` needs to know which files are supposed to be in the zipfile and thus in the destinationdirectory in their extracted form.\n\n### \u003ca name=\"parsing\"\u003e\u003c/a\u003eReading / parsing geonames.org data\n\nOnce files are downloaded using the `GeoFileDownloader`, *or* by using your own custom/specific implementation, the files can be accessed using the `GeoFileReader` class. This class contains a number of static \"convenience methods\" like `ReadGeoNames()` and it's \"sibling\" `ReadExtendedGeoNames()`. but also `ReadCountryInfo()`, `ReadAlternateNames()` etc. There is a \"convenience method\" for each entity.\n\n```c#\n// Open file \"cities1000.txt\" and retrieve only cities in the US\nvar cities_in_us = GeoFileReader.ReadExtendedGeoNames(@\"D:\\my\\geodata\\cities1000.txt\")\n        .Where(p =\u003e p.CountryCode.Equals(\"US\", StringComparison.OrdinalIgnoreCase))\n        .OrderBy(p =\u003e p.Name);\n```\n\nAgain, **please note** that `Read\u003cSomething\u003e` methods return an `IEnumerable\u003cT\u003e`. Whenever you want to access the data more than once you will probably want to call `.ToArray()` or similar to materialize the data into memory. The `GeoFileReader` class has two static method (`ReadBuiltInContinents()` and `ReadBuiltInFeatureClasses()`) that can be used to use built-in values for continents and [feature codes](http://www.geonames.org/export/codes.html) which are not provided by geonames.org as downloadable files. You can, however, craft your own files for this purpose and use the `ReadContinents()` and `ReadFeatureClasses()` if you want to specify your own values / update built-in values (should `NGeoNames`'s values be outdated for example).\n\nYou can also add your own entities and, as long as you provide a parser for it, use the `GeoFileReader` class to read/parse files for these entities as well:\n\n```c#\nvar data = new GeoFileReader().ReadRecords\u003cMyEntity\u003e(\"d:\\foo\\bar.txt\", new MyEntityParser());\n```\n\nAs long as your parser implements `IParser\u003cMyEntity\u003e` you're good to go. A parser can skip a fixed number of lines in a file (for example a 'header' record), skip comments (for example lines starting with `#`) and you can even specify the encoding to use etc. Examples and more information can be found in the unittests.\n\nAnother thing to note is that the `GeoFileReader` will try to \"autodetect\" if the file is a plain text file (`.txt` extension) or a GZipped file (`.gz` extension). Support for GZip was added to keep the footprint of the files lower when desired. This will, however, trade-off I/O speed and CPU load for space. The `ReadRecords\u003cT\u003e()` method has an overload where you can explicitly specify the type of the file (should you want to use your own file-extensions like `.dat` for example).\n\n\u003e Support for compressing downloaded files using the `GeoFileDownloader` on the fly is planned for a later version; for now you will have to GZip the files manually.\n\nThe `GeoFileReader` also supports the use of [`Stream`](http://msdn.microsoft.com/en-us/library/system.io.stream.aspx)s so you can provide data from a MemoryStream for example or any other source that can be wrapped in a stream.\n\nAs you'll probably realize by now, the `GeoFileReader` class *combined* with [LINQ](http://msdn.microsoft.com/en-us/library/bb397926.aspx) allows for very powerful querying, filtering and sorting of the data. Combine it with the `GeoFileWriter` to persist custom datasets (custom \"materialized views\") and the sky is the limit.\n\n### \u003ca name=\"utilizing\"\u003e\u003c/a\u003eUtilizing geonames.org data\n\nThe 'heart' of the library is the `ReverseGeoCode\u003cT\u003e` class. When you supply it with either `IEnumerable\u003cGeoNames\u003e` or `IEnumerable\u003cExtendedGeoNames\u003e` it can be used to do a `RadialSearch()` or `NearestNeighbourSearch()`. Supplying the class with data can be done by either passing it to the class constructor or by using the `Add()` or `AddRange()` methods. You may want to call the `Balance()` method to balance the internal KD-tree, however; this is done automatically when the data is supplied via the constructor. Even if you choose to store your data in a database or custom (binary?) fileformat or anything else; as long as you provide an `IEnumerable` to this class you'll be able to use it.\n\n```c#\n// Create our ReverseGeoCode class and supply it with data\nvar r = new ReverseGeoCode\u003cExtendedGeoName\u003e(\n        GeoFileReader.ReadExtendedGeoNames(@\"D:\\foo\\cities1000.txt\")\n    );\n            \n// Create a point from a lat/long pair from which we want to conduct our search(es) (center)\nvar new_york = r.CreateFromLatLong(40.7056308, -73.9780035);\n\n// Find 10 nearest\nr.NearestNeighbourSearch(new_york, 10);\n```\n\nOfcourse there's no need to dabble with lat/long at all:\n\n```c#\n// Read data into memory\nvar data = GeoFileReader.ReadExtendedGeoNames(@\"D:\\foo\\cities1000.txt\")\n        .ToDictionary(p =\u003e p.Id);\n\n// Find New York by it's geoname ID (O(1) lookup)\nvar new_york = data[5128581];\n\n// Find 10 nearest\nvar r = new ReverseGeoCode\u003cExtendedGeoName\u003e(data.Values);\nr.NearestNeighbourSearch(new_york, 10);\n```\n\nOr simply find by name:\n\n\n```c#\n// Read data into memory\nvar data = GeoFileReader.ReadExtendedGeoNames(@\"D:\\foo\\cities1000.txt\")\n        .ToArray();\n\n// Find New York by it's name (linear search, O(n))\nvar new_york = data.Where(p =\u003e p.Name.Equals(\"New York City\")).First();\n\n// Find 10 nearest\nvar r = new ReverseGeoCode\u003cExtendedGeoName\u003e(data);\nr.NearestNeighbourSearch(new_york, 10);\n```\n\nDepending on how you want to search/use the underlying data you may want to use other, more optimal, datastructures than demonstrated above. It's up to you!\n\nNote that the library is based on the [**International System of Units (SI)**](http://en.wikipedia.org/wiki/International_System_of_Units); units of distance are specified in **meters**. If you want to use the imperial system (e.g. miles, nautical miles, yards, foot and whathaveyou's) you need to convert to/from meters. The `GeoUtil` class provides helper-methods for converting miles/yards to meters and vice versa.\n\nThe `GeoName` class (and, by extension, the `ExtendedGeoName` class) has a `DistanceTo()` method which can be used to determine the exact distance betweem two points.\n\nBoth the `NearestNeighbourSearch()` and `RadialSearch()` methods have some overloads that accept lat/long pairs as *doubles* as well.\n\n### \u003ca name=\"composing\"\u003e\u003c/a\u003eWriting / composing geonames.org data\n\nThe `NGeoNames.Composers` namespace holds composers (the opposite of parsers) to enable you to write geoname.org datafiles. For this you can use the `GeoNameFileWriter` class which, like the `GeoNameFileReader` class, has generic methods for writing records (`WriteRecords\u003cT\u003e`) and static \"convenience methods\" to write specific entities to a file. If you wanted to 'transform' a file like `allcountries.txt` to a file with data from, say, the [Benelux](http://en.wikipedia.org/wiki/Benelux)) you could supply the `GeoNameFileWriter` with data from `BE.txt`, `NL.txt` and `LU.txt` *or* data from `allcountries.txt` or `cities1000.txt` filtered with a LINQ query to only data from these countries.\n\nBelow is an example of what this would look like (with an extra filter added to filter out records with `population \u003c 1000`):\n\n```c#\n// Filter 'allcountries.txt' to only BE, NL, LU entries with a population of \u003e= 1000\nGeoFileWriter.WriteExtendedGeoNames(@\"d:\\foo\\benelux1000.txt\",\n   GeoFileReader.ReadExtendedGeoNames(@\"d:\\foo\\allcountries.txt\")\n      .Where(e =\u003e new[] { \"BE\", \"NL\", \"LU\" }.Contains(e.CountryCode) \u0026\u0026 e.Population \u003e= 1000)\n      .OrderBy(e =\u003e e.CountryCode).ThenBy(e =\u003e e.Name)\n);\n\n// ...or...\n\n// Join BE, NL en LU datasets, filter records with a population of \u003e= 1000\nGeoFileWriter.WriteExtendedGeoNames(@\"d:\\foo\\benelux1000.txt\",\n   GeoFileReader.ReadExtendedGeoNames(@\"d:\\foo\\BE.txt\")\n      .Union(GeoFileReader.ReadExtendedGeoNames(@\"d:\\foo\\NL.txt\"))\n      .Union(GeoFileReader.ReadExtendedGeoNames(@\"d:\\foo\\LU.txt\"))\n        .Where(e =\u003e e.Population \u003e= 1000)\n        .OrderBy(e =\u003e e.CountryCode).ThenBy(e =\u003e e.Name)\n);\n\n```\n\n### A word about \"extended format\"\n\nThe `GeoNamesReader` and `GeoNamesWriter` and the (Extended)GeoName parsers/composers always assume the `ExtendedGeoName` format (e.g. 19 fields of data) unless explicitly specified. The parameter **extendedfileformat** may pop-up on some method overloads; whenever this parameter is passed `false` the class will assume a 'simple' (or non-extended) format with only 4 fields of data: Id, Name, Latitude and Longitude. This format is more compact; especially when writing `GeoName` entities instead of `ExtendedGeoName` entities to a file. However, to remain compatible with the original files you probably don't want to use this 'simple' format. Make sure you understand the consequences before you do!\n\n## Help\n\nThe [NuGet package](https://www.nuget.org/packages/NGeoNames/) comes with a Windows Help File (`NGeonames.chm`) with lots more information. You can also build this help file, or other formats, yourself using [Sandcastle Help File Builder](https://shfb.codeplex.com/). And finally you can use the richly commented code if you don't want to build or use help files.\n\n## Project status\n\n\u003cimg src=\"http://riii.nl/womm\" width=\"200\" height=\"200\" align=\"left\"\u003e The project will be updated from time-to-time when required. I am happy to accept pull-requests; if you're interested in contributing to this library please contact me. If you have any issues please [open an issue](https://github.com/RobThree/NGeoNames/issues).\n\n[![Build status](https://ci.appveyor.com/api/projects/status/mkmbxvm1w0mxaifv)](https://ci.appveyor.com/project/RobIII/ngeonames) \u003ca href=\"https://www.nuget.org/packages/NGeoNames/\"\u003e\u003cimg src=\"http://img.shields.io/nuget/v/NGeoNames.svg?style=flat-square\" alt=\"NuGet version\" height=\"18\"\u003e\u003c/a\u003e\n\n## License\n\nLicensed under MIT license. See [LICENSE](LICENSE) for details.\n\n[Logo / icon](http://www.iconninja.com/earth-search-internet-icon-44388) sourced from iconninja.com ([Archived page](http://riii.me/rftqo))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobthree%2Fngeonames","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobthree%2Fngeonames","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobthree%2Fngeonames/lists"}