geodb-rs 0.1.5__tar.gz → 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/Cargo.lock +7 -6
  2. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/Cargo.toml +14 -2
  3. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/PKG-INFO +1 -1
  4. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/Cargo.toml +4 -0
  5. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/README.md +150 -21
  6. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/legacy_model/search.rs +7 -3
  7. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/loader/builder.rs +45 -3
  8. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/model/mod.rs +2 -0
  9. geodb_rs-0.1.6/crates/geodb-core/src/model/query.rs +471 -0
  10. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/prelude.rs +1 -1
  11. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-py/Cargo.toml +1 -1
  12. geodb_rs-0.1.6/crates/geodb-py/geodb_rs_data/geodb.nested.comp.blobs.bin +0 -0
  13. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-py/src/lib.rs +53 -0
  14. geodb_rs-0.1.5/crates/geodb-py/geodb_rs_data/city_meta.json +0 -199
  15. geodb_rs-0.1.5/crates/geodb-py/geodb_rs_data/countries+states+cities.json.gz +0 -0
  16. geodb_rs-0.1.5/geodb_rs_data/geodb.flat.comp.blobs.bin +0 -0
  17. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/README.md +0 -0
  18. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/benches/benchmarks.rs +0 -0
  19. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/build.rs +0 -0
  20. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/alias.rs +0 -0
  21. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/api.rs +0 -0
  22. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/bin/profile_search.rs +0 -0
  23. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/common/mod.rs +0 -0
  24. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/common/raw.rs +0 -0
  25. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/common/raw_normalize.rs +0 -0
  26. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/error.rs +0 -0
  27. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/legacy_model/convert.rs +0 -0
  28. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/legacy_model/mod.rs +0 -0
  29. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/legacy_model/nested.rs +0 -0
  30. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/lib.rs +0 -0
  31. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/loader/binary_load.rs +0 -0
  32. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/loader/common_io.rs +0 -0
  33. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/loader/mod.rs +0 -0
  34. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/model/convert.rs +0 -0
  35. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/model/flat.rs +0 -0
  36. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/model/search.rs +0 -0
  37. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/spatial.rs +0 -0
  38. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/text.rs +0 -0
  39. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/src/traits.rs +0 -0
  40. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/tests/basic.rs +0 -0
  41. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-core/tests/city_meta_alias_search.rs +0 -0
  42. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-py/README.md +0 -0
  43. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-py/example.py +0 -0
  44. {geodb_rs-0.1.5/crates/geodb-core/data → geodb_rs-0.1.6/crates/geodb-py/geodb_rs_data}/city_meta.json +0 -0
  45. {geodb_rs-0.1.5/crates/geodb-core/data → geodb_rs-0.1.6/crates/geodb-py/geodb_rs_data}/countries+states+cities.json.gz +0 -0
  46. {geodb_rs-0.1.5/crates/geodb-core/data → geodb_rs-0.1.6/crates/geodb-py/geodb_rs_data}/geodb.flat.comp.blobs.bin +0 -0
  47. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/crates/geodb-py/tests/test_basic.py +0 -0
  48. {geodb_rs-0.1.5/crates/geodb-py → geodb_rs-0.1.6}/geodb_rs_data/geodb.flat.comp.blobs.bin +0 -0
  49. {geodb_rs-0.1.5 → geodb_rs-0.1.6}/pyproject.toml +0 -0
@@ -531,7 +531,7 @@ dependencies = [
531
531
 
532
532
  [[package]]
533
533
  name = "geodb-cli"
534
- version = "0.1.5"
534
+ version = "0.1.6"
535
535
  dependencies = [
536
536
  "anyhow",
537
537
  "clap",
@@ -540,7 +540,7 @@ dependencies = [
540
540
 
541
541
  [[package]]
542
542
  name = "geodb-core"
543
- version = "0.1.5"
543
+ version = "0.1.6"
544
544
  dependencies = [
545
545
  "bincode",
546
546
  "criterion",
@@ -556,19 +556,20 @@ dependencies = [
556
556
 
557
557
  [[package]]
558
558
  name = "geodb-ffi"
559
- version = "0.1.5"
559
+ version = "0.1.6"
560
560
  dependencies = [
561
561
  "bincode",
562
562
  "flate2",
563
563
  "geodb-core",
564
564
  "once_cell",
565
565
  "serde",
566
+ "serde_json",
566
567
  "uniffi",
567
568
  ]
568
569
 
569
570
  [[package]]
570
571
  name = "geodb-py"
571
- version = "0.1.5"
572
+ version = "0.1.6"
572
573
  dependencies = [
573
574
  "geodb-core",
574
575
  "pyo3",
@@ -579,14 +580,14 @@ dependencies = [
579
580
 
580
581
  [[package]]
581
582
  name = "geodb-rs"
582
- version = "0.1.5"
583
+ version = "0.1.6"
583
584
  dependencies = [
584
585
  "geodb-core",
585
586
  ]
586
587
 
587
588
  [[package]]
588
589
  name = "geodb-wasm"
589
- version = "0.1.5"
590
+ version = "0.1.6"
590
591
  dependencies = [
591
592
  "bincode",
592
593
  "console_error_panic_hook",
@@ -14,7 +14,7 @@ members = ["crates/geodb-py"]
14
14
  resolver = "2"
15
15
 
16
16
  [workspace.package]
17
- version = "0.1.5"
17
+ version = "0.1.6"
18
18
  edition = "2021"
19
19
  license = "MIT"
20
20
  repository = "https://github.com/holg/geodb-rs"
@@ -31,7 +31,19 @@ serde-wasm-bindgen = "0.6"
31
31
  serde_json = { version = "1" }
32
32
  thiserror = "1"
33
33
  wasm-bindgen = "=0.2.105" # Pinned to match trunk 0.21.14's bundled wasm-bindgen-cli
34
- web-sys = { version = "0.3", features = ["console"] }
34
+ web-sys = { version = "0.3", features = [
35
+ "console",
36
+ "Window",
37
+ "Document",
38
+ "Location",
39
+ "Element",
40
+ "HtmlElement",
41
+ "HtmlInputElement",
42
+ "Event",
43
+ "EventTarget",
44
+ "UrlSearchParams",
45
+ "CssStyleDeclaration"
46
+ ] }
35
47
 
36
48
  [dependencies]
37
49
  geodb-core = { path = "crates/geodb-core" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geodb-rs
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Classifier: Programming Language :: Python
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -10,6 +10,10 @@ readme = "README.md"
10
10
  documentation = "https://docs.rs/geodb-core"
11
11
  keywords = ["geography", "database", "countries", "cities", "geo"]
12
12
  categories = ["database", "data-structures"]
13
+ # Exclude all data files from published package (reduces package to <1MB)
14
+ # Data is automatically downloaded from GitHub on first load
15
+ # Download requires the 'builder' feature (enabled by default)
16
+ exclude = ["data/*.bin", "data/*.json.gz", "data/*.json"]
13
17
 
14
18
  # Configure docs.rs build
15
19
  [package.metadata.docs.rs]
@@ -20,6 +20,10 @@
20
20
  [![PyPI Downloads](https://img.shields.io/pypi/dm/geodb-rs.svg?label=downloads)](https://pypi.org/project/geodb-rs/)
21
21
  [![Python Versions](https://img.shields.io/pypi/pyversions/geodb-rs.svg)](https://pypi.org/project/geodb-rs/)
22
22
 
23
+ ### pub.dev (Flutter)
24
+ [![pub.dev](https://img.shields.io/pub/v/geodb_flutter.svg)](https://pub.dev/packages/geodb_flutter)
25
+ [![pub.dev likes](https://img.shields.io/pub/likes/geodb_flutter)](https://pub.dev/packages/geodb_flutter)
26
+
23
27
  ### App Store
24
28
  [![iOS](https://img.shields.io/badge/iOS-App%20Store-blue?logo=apple)](https://apps.apple.com/app/geodb-rs/id6755972245)
25
29
  [![TestFlight](https://img.shields.io/badge/TestFlight-Beta-orange?logo=apple)](https://testflight.apple.com/join/TuFejJEq)
@@ -33,6 +37,7 @@ This repository is a **Cargo workspace** containing:
33
37
  - **`geodb-wasm`** — WebAssembly bindings + browser demo — docs: https://docs.rs/geodb-wasm
34
38
  - **`geodb-py`** — Python bindings (published on PyPI as "geodb-rs") — https://pypi.org/project/geodb-rs/
35
39
  - **`geodb-ffi`** — FFI bindings for mobile platforms (iOS, macOS, watchOS, Android)
40
+ - **`geodb_flutter`** — Flutter plugin (published on pub.dev) — https://pub.dev/packages/geodb_flutter
36
41
 
37
42
  ---
38
43
 
@@ -55,13 +60,21 @@ The dataset is adapted from
55
60
  https://github.com/dr5hn/countries-states-cities-database
56
61
  (licensed under **CC-BY-4.0**, attribution required).
57
62
 
58
- > Important: Data source we rely on
63
+ > **Important: Data source and automatic downloading**
59
64
  >
60
- > geodb-core ships and expects the upstream dataset from the following file in the dr5hn/countries-states-cities-database repository:
65
+ > geodb-core uses the upstream dataset from the dr5hn/countries-states-cities-database repository:
61
66
  >
62
67
  > https://github.com/dr5hn/countries-states-cities-database/blob/master/json/countries%2Bstates%2Bcities.json.gz
63
68
  >
64
- > The default loader uses a copy of this file placed under `crates/geodb-core/data/countries+states+cities.json.gz` and builds a binary cache alongside it. If you update or replace the dataset, ensure it retains the same JSON structure. Please observe the CC-BY-4.0 license and attribution of the upstream project.
69
+ > **Automatic data download and caching:**
70
+ > - The published crate does NOT include data files (keeps package size under 1MB)
71
+ > - **On first load**, the library automatically downloads the dataset from GitHub (~3.7MB)
72
+ > - After download, a binary cache is generated for fast subsequent loads
73
+ > - Download and cache generation happen only once per system
74
+ > - Requires the `builder` feature (enabled by default) and internet connection for first load
75
+ > - Downloaded data and cache stored in `crates/geodb-core/data/` directory
76
+ >
77
+ > If you update or replace the dataset, ensure it retains the same JSON structure. Please observe the CC-BY-4.0 license and attribution of the upstream project.
65
78
 
66
79
  ---
67
80
 
@@ -71,14 +84,16 @@ https://github.com/dr5hn/countries-states-cities-database
71
84
 
72
85
  ```toml
73
86
  [dependencies]
74
- geodb-core = "0.2"
87
+ geodb-core = "0.1"
75
88
  ```
76
89
 
90
+ **Note:** First load will download the dataset from GitHub (~3.7MB) and build the binary cache (requires internet connection). Subsequent loads will be instant using the cached binary.
91
+
77
92
  ### For WebAssembly (browser/Node)
78
93
 
79
94
  ```toml
80
95
  [dependencies]
81
- geodb-wasm = "0.2"
96
+ geodb-wasm = "0.1"
82
97
  ```
83
98
 
84
99
  ### For Swift (iOS, macOS, watchOS)
@@ -114,7 +129,25 @@ for city in results {
114
129
  let nearest = engine.findNearest(lat: 52.52, lng: 13.405, count: 10)
115
130
  ```
116
131
 
117
- ### For Android (Kotlin)
132
+ ### For Flutter (iOS, Android, macOS)
133
+
134
+ ```yaml
135
+ # pubspec.yaml
136
+ dependencies:
137
+ geodb_flutter: ^0.1.8
138
+ ```
139
+
140
+ ```dart
141
+ import 'package:geodb_flutter/geodb_flutter.dart';
142
+
143
+ final geodb = GeodbFlutter();
144
+ await geodb.initialize();
145
+
146
+ final results = await geodb.smartSearch('Berlin');
147
+ final nearest = await geodb.findNearest(lat: 52.52, lng: 13.405, count: 10);
148
+ ```
149
+
150
+ ### For Android (Kotlin - Native)
118
151
 
119
152
  See the example app in `GeoDB-App/android-app/`. The app uses UniFFI-generated Kotlin bindings.
120
153
 
@@ -263,21 +296,46 @@ if let Some(us) = db.find_country_by_iso2("US") {
263
296
  let countries = db.find_countries_by_phone_code("+44");
264
297
  ```
265
298
 
266
- ### Search for cities named "Springfield"
299
+ ### Filter-based city search (CityQuery API)
300
+
301
+ Use the chainable `query_cities()` API to disambiguate cities with common names:
267
302
 
268
303
  ```rust
269
- let results: Vec<_> = db.countries()
270
- .iter()
271
- .flat_map(|country| {
272
- country.states().iter().flat_map(move |state| {
273
- state.cities().iter()
274
- .filter(|c| c.name() == "Springfield")
275
- .map(move |c| (country.name(), state.name(), c.name()))
276
- })
277
- })
304
+ use geodb_core::prelude::*;
305
+
306
+ let db = GeoDb::<DefaultBackend>::load()?;
307
+
308
+ // Find Springfield in Illinois, US (not the 30+ other Springfields!)
309
+ let results = db.query_cities()
310
+ .filter_country("US")
311
+ .filter_region("Illinois")
312
+ .filter_city("Springfield")
278
313
  .collect();
314
+
315
+ assert_eq!(results.len(), 1);
316
+ let (city, state, country) = &results[0];
317
+ println!("{} - {}, {}", city.name(), state.name(), country.iso2());
318
+ // Output: Springfield - Illinois, US
319
+
320
+ // Find Lüdinghausen in NRW (accent-insensitive search)
321
+ let city = db.query_cities()
322
+ .filter_country("DE")
323
+ .filter_region("Nordrhein-Westfalen")
324
+ .filter_city("Ludinghausen") // works without umlaut too
325
+ .first();
326
+
327
+ // Count all Springfields worldwide
328
+ let count = db.query_cities()
329
+ .filter_city("Springfield")
330
+ .count();
331
+ println!("Found {} cities named Springfield", count);
279
332
  ```
280
333
 
334
+ Filters support:
335
+ - **Country**: ISO2/ISO3 codes (`"US"`, `"DEU"`) or name substring (`"Germany"`)
336
+ - **Region**: State code (`"CA"`, `"NW"`) or name (`"California"`, `"Nordrhein-Westfalen"`)
337
+ - **City**: Name substring with accent-insensitive matching
338
+
281
339
  ---
282
340
 
283
341
  # WebAssembly (`geodb-wasm`)
@@ -316,13 +374,31 @@ Install:
316
374
  cargo install geodb-cli
317
375
  ```
318
376
 
319
- Examples:
377
+ Commands:
320
378
 
321
379
  ```bash
322
- geodb-cli --help
323
- geodb-cli stats
324
- geodb-cli find-country US
325
- geodb-cli list-cities --country US --state CA
380
+ geodb-cli --help # Show all commands
381
+ geodb-cli stats # Database statistics
382
+ geodb-cli countries # List all countries
383
+ geodb-cli country US # Lookup country by ISO2/ISO3 code
384
+ geodb-cli states US # List all states for a country
385
+ geodb-cli cities "Springfield" # Search cities by substring
386
+ geodb-cli smart "Berlin" # Smart search (cities/states/countries)
387
+ geodb-cli nearest --lat 52.52 --lng 13.405 -n 5 # Find 5 nearest cities
388
+ geodb-cli radius --lat 52.52 --lng 13.405 -r 50 # Cities within 50km
389
+ ```
390
+
391
+ Filter-based query (disambiguate cities):
392
+
393
+ ```bash
394
+ # Find Springfield in Illinois, US (not the 30+ other Springfields!)
395
+ geodb-cli query --city Springfield --country US --region Illinois
396
+
397
+ # Find Lüdinghausen in NRW, Germany
398
+ geodb-cli query --city "Lüdinghausen" --country DE --region "Nordrhein-Westfalen"
399
+
400
+ # List all cities in Bavaria
401
+ geodb-cli query --country Germany --region Bavaria -n 50
326
402
  ```
327
403
 
328
404
  Docs.rs: https://docs.rs/geodb-cli
@@ -365,6 +441,59 @@ print(db.stats()) # (countries, states, cities)
365
441
 
366
442
  ---
367
443
 
444
+ # Flutter Plugin (`geodb_flutter`)
445
+
446
+ Cross-platform Flutter plugin with native Rust performance.
447
+
448
+ ### pub.dev
449
+ [![pub.dev](https://img.shields.io/pub/v/geodb_flutter.svg)](https://pub.dev/packages/geodb_flutter)
450
+
451
+ ### Installation
452
+
453
+ ```yaml
454
+ dependencies:
455
+ geodb_flutter: ^0.1.8
456
+ ```
457
+
458
+ ### Platform Support
459
+
460
+ | Platform | Status | Architectures |
461
+ |----------|--------|---------------|
462
+ | iOS | Ready | arm64 device, arm64 simulator |
463
+ | macOS | Ready | arm64, x86_64 (Universal) |
464
+ | Android | Ready | arm64-v8a, armeabi-v7a, x86_64, x86 |
465
+
466
+ ### Quick Start
467
+
468
+ ```dart
469
+ import 'package:geodb_flutter/geodb_flutter.dart';
470
+
471
+ final geodb = GeodbFlutter();
472
+
473
+ // Initialize (required first)
474
+ await geodb.initialize();
475
+
476
+ // Get stats
477
+ final stats = await geodb.getStats();
478
+ print('${stats.cities} cities in ${stats.countries} countries');
479
+
480
+ // Smart search
481
+ final results = await geodb.smartSearch('Berlin');
482
+ for (final city in results) {
483
+ print('${city.name}, ${city.country} (${city.iso2})');
484
+ }
485
+
486
+ // Find nearest cities
487
+ final nearest = await geodb.findNearest(lat: 52.52, lng: 13.405, count: 10);
488
+
489
+ // Search within radius
490
+ final nearby = await geodb.findInRadius(lat: 52.52, lng: 13.405, radiusKm: 50.0);
491
+ ```
492
+
493
+ For a complete example, see `GeoDB-Apps/geodb_city_autocomplete/` in the repository.
494
+
495
+ ---
496
+
368
497
  # Mobile Apps (`GeoDB-App`)
369
498
 
370
499
  The repository includes native apps for Apple and Android platforms:
@@ -495,7 +495,7 @@ impl<B: GeoBackend> GeoSearch<B> for GeoDb<B> {
495
495
  }
496
496
  None
497
497
  }
498
- fn find_nearest(&self, lat: f64, lng: f64, count: usize) -> Vec<&City<B>> {
498
+ fn find_nearest(&self, lat: f64, lng: f64, count: usize) -> Vec<CityContext<'_, B>> {
499
499
  // Legacy: We don't have a spatial_index, so we must scan the whole world.
500
500
  // This is slower (O(N)), but correct.
501
501
 
@@ -509,7 +509,7 @@ impl<B: GeoBackend> GeoSearch<B> for GeoDb<B> {
509
509
 
510
510
  // Squared Euclidean for fast sorting
511
511
  let dist = distance_squared(lat, lng, c_lat, c_lng);
512
- candidates.push((dist, city));
512
+ candidates.push((dist, city, state, country));
513
513
  }
514
514
  }
515
515
  }
@@ -518,7 +518,11 @@ impl<B: GeoBackend> GeoSearch<B> for GeoDb<B> {
518
518
  candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
519
519
 
520
520
  // Take top N
521
- candidates.into_iter().take(count).map(|(_, c)| c).collect()
521
+ candidates
522
+ .into_iter()
523
+ .take(count)
524
+ .map(|(_, c, s, co)| (c, s, co))
525
+ .collect()
522
526
  }
523
527
 
524
528
  // crates/geodb-core/src/model/search.rs
@@ -9,16 +9,19 @@ use crate::common::raw::CountryRaw;
9
9
  use crate::common::raw_normalize::apply_all_metadata;
10
10
  use crate::error::{GeoError, Result};
11
11
  use std::fs::{self, File};
12
- use std::io::BufWriter;
12
+ use std::io::{BufWriter, Write};
13
13
  use std::path::Path;
14
14
 
15
15
  #[cfg(feature = "compact")]
16
16
  use flate2::{write::GzEncoder, Compression};
17
17
 
18
+ // Raw download URL for the dataset (GitHub raw content)
19
+ const DATA_DOWNLOAD_URL: &str = "https://raw.githubusercontent.com/dr5hn/countries-states-cities-database/master/json/countries%2Bstates%2Bcities.json.gz";
20
+
18
21
  // Extends GeoDb with Builder/Source capabilities
19
22
  impl GeoDb<DefaultBackend> {
20
23
  /// **Smart Builder Logic:**
21
- /// Checks cache -> Loads Binary OR Builds Source -> Writes Cache.
24
+ /// Checks cache -> Downloads if missing -> Loads Binary OR Builds Source -> Writes Cache.
22
25
  pub(super) fn load_via_builder(path: &Path, filter: Option<&[&str]>) -> Result<Self> {
23
26
  let cache_path = common_io::get_cache_path(path);
24
27
 
@@ -30,7 +33,16 @@ impl GeoDb<DefaultBackend> {
30
33
  }
31
34
  }
32
35
 
33
- // 2. Build (Slow)
36
+ // 2. Download if source file doesn't exist
37
+ if !path.exists() {
38
+ eprintln!(
39
+ "Data file not found at {:?}, downloading from GitHub...",
40
+ path
41
+ );
42
+ Self::download_dataset(path)?;
43
+ }
44
+
45
+ // 3. Build (Slow)
34
46
  // This converts JSON -> Active Structs (Flat or Nested)
35
47
  let db = Self::build_from_source(path)?;
36
48
 
@@ -158,4 +170,34 @@ impl GeoDb<DefaultBackend> {
158
170
 
159
171
  Ok(())
160
172
  }
173
+
174
+ /// Downloads the dataset from GitHub to the specified path
175
+ fn download_dataset(dest_path: &Path) -> Result<()> {
176
+ // Ensure parent directory exists
177
+ if let Some(parent) = dest_path.parent() {
178
+ fs::create_dir_all(parent).map_err(GeoError::Io)?;
179
+ }
180
+
181
+ // Download using reqwest (blocking)
182
+ let response = reqwest::blocking::get(DATA_DOWNLOAD_URL)
183
+ .map_err(|e| GeoError::InvalidData(format!("Failed to download dataset: {}", e)))?;
184
+
185
+ if !response.status().is_success() {
186
+ return Err(GeoError::InvalidData(format!(
187
+ "Failed to download dataset: HTTP {}",
188
+ response.status()
189
+ )));
190
+ }
191
+
192
+ // Write to file
193
+ let mut file = File::create(dest_path).map_err(GeoError::Io)?;
194
+ let bytes = response
195
+ .bytes()
196
+ .map_err(|e| GeoError::InvalidData(format!("Failed to read response: {}", e)))?;
197
+
198
+ file.write_all(&bytes).map_err(GeoError::Io)?;
199
+
200
+ eprintln!("Downloaded {} bytes to {:?}", bytes.len(), dest_path);
201
+ Ok(())
202
+ }
161
203
  }
@@ -54,6 +54,7 @@
54
54
  pub mod flat;
55
55
  // pub mod region;
56
56
  pub mod convert;
57
+ pub mod query;
57
58
  pub mod search;
58
59
  // Re-exports for convenience
59
60
  pub use super::{CityView, CountryView, StateView};
@@ -63,6 +64,7 @@ pub use crate::common::DbStats;
63
64
  pub use crate::error::{GeoDbError, GeoError, Result};
64
65
  pub use crate::text::{equals_folded, fold_ascii_lower, fold_key};
65
66
  pub use flat::{City, Country, CountryTimezone, GeoDb, State};
67
+ pub use query::CityQuery;
66
68
  #[cfg(not(feature = "compact"))]
67
69
  pub const CACHE_SUFFIX: &str = ".flat.bin";
68
70
  #[cfg(feature = "compact")]