@code-plate/iran-cities 1.0.2 → 1.2.0
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.
- package/CHANGELOG.md +10 -0
- package/NOTICE.md +21 -0
- package/README.fa.md +56 -20
- package/README.md +56 -20
- package/dist/index.cjs +5089 -1173
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +14 -33
- package/dist/index.d.ts +14 -33
- package/dist/index.mjs +5083 -1173
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +5104 -1175
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.mts +11 -20
- package/dist/react.d.ts +11 -20
- package/dist/react.mjs +5105 -1176
- package/dist/react.mjs.map +1 -1
- package/dist/{types-DwFZSr9I.d.mts → types-DwK3g-Z0.d.mts} +25 -6
- package/dist/{types-DwFZSr9I.d.ts → types-DwK3g-Z0.d.ts} +25 -6
- package/package.json +12 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
- **Added:** numeric `id` for provinces and cities (globally unique, stable); `isCapital`, `capitalId`, province `phoneCode`.
|
|
5
|
+
- **Added:** `coordinates: { lat, lng, confidence }` for 1152 cities (GeoNames, CC BY 4.0); `confidence` is `'high' | 'medium' | 'low'`.
|
|
6
|
+
- **Added:** `getCityById`, `getProvinceById`, `getCitiesByName`, `getCapital`, `getCapitals`, `getProvinceByPhoneCode`; lookups accept numeric ids.
|
|
7
|
+
- **Added:** `npm run validate` data-integrity checks.
|
|
8
|
+
- **Fixed:** duplicate «گلوگاه» entry in Mazandaran removed (1253 -> 1252 cities); `useCitySearch` / `useProvinceSearch` no longer re-run on inline `options`.
|
|
9
|
+
- **Corrected data (old names/slugs still resolve):** city 402 `sowme'eh-sara` -> `sowmeeh-sara` (fa: صومعه سرا); city 672 `هلشی` -> `halashi` (fa: هلشی). Ids unchanged.
|
|
10
|
+
- **Corrected coordinates:** Ahvaz and Kermanshah capital coordinates fixed (source table was ~110 km / ~60 km off).
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Attribution / منابع و مجوز دادهها
|
|
2
|
+
|
|
3
|
+
Coordinates (`lat`/`lng`) in `src/data.json` are point locations from **GeoNames** (https://www.geonames.org),
|
|
4
|
+
licensed under **Creative Commons Attribution 4.0** (CC BY 4.0). Attribution to GeoNames is required.
|
|
5
|
+
|
|
6
|
+
- Source file: GeoNames Iran dump `IR.zip` (https://download.geonames.org/export/dump/IR.zip), supplied by the package author on 2026-10-07.
|
|
7
|
+
- The 31 provincial-capital coordinates were supplied by the package author (Wikipedia table), cross-checked against GeoNames;
|
|
8
|
+
two were corrected (Ahvaz, Kermanshah).
|
|
9
|
+
- Each city was matched to a GeoNames populated place by normalized Persian name within the same province
|
|
10
|
+
(province mapped through GeoNames admin1 codes), with a Latin-name fallback. Abandoned/destroyed/historical places were excluded.
|
|
11
|
+
|
|
12
|
+
The following public datasets were used **only to cross-check the match** (no values were copied from them unless they equal the GeoNames value):
|
|
13
|
+
|
|
14
|
+
| Dataset | Used for | License |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| sajaddp/list-of-cities-in-Iran | cross-check; official county seats (GeoNames-derived) | repo: GPL-3.0; coordinate records: CC BY 4.0 (GeoNames) |
|
|
17
|
+
| dr5hn/countries-states-cities-database | cross-check only | ODbL-1.0 |
|
|
18
|
+
| jafaripur/iran-locality-database | cross-check only | none stated |
|
|
19
|
+
| mhndev/iran-geography | cross-check only | none stated |
|
|
20
|
+
|
|
21
|
+
This is a summary, not legal advice.
|
package/README.fa.md
CHANGED
|
@@ -26,8 +26,10 @@ pnpm add @code-plate/iran-cities
|
|
|
26
26
|
## دادهها
|
|
27
27
|
|
|
28
28
|
- `۳۱` استان
|
|
29
|
-
-
|
|
30
|
-
- هر رکورد شامل `en` (شناسه لاتین kebab-case، مثل `tehran`) و `fa` (نام فارسی، مثل `تهران`) است.
|
|
29
|
+
- `۱۲۵۲` شهر
|
|
30
|
+
- هر رکورد شامل `id` (شناسهی عددی یکتا و پایدار)، `en` (شناسه لاتین kebab-case، مثل `tehran`) و `fa` (نام فارسی، مثل `تهران`) است.
|
|
31
|
+
- هر استان `phoneCode` (کد تلفن بینشهری) و `capitalId` (id مرکز استان) دارد؛ هر شهر `isCapital` دارد و مراکز استانها `coordinates: { lat, lng }` هم دارند.
|
|
32
|
+
- `id` شهرها در **کل کشور یکتاست** (شهرهای همنام مثل «مهاباد» در دو استان، دو id متفاوت دارند) و بین نسخهها تغییر نمیکند؛ شهر جدید همیشه id بعدی را میگیرد.
|
|
31
33
|
|
|
32
34
|
---
|
|
33
35
|
|
|
@@ -40,6 +42,9 @@ import {
|
|
|
40
42
|
getProvince,
|
|
41
43
|
getCities,
|
|
42
44
|
getCity,
|
|
45
|
+
getCityById,
|
|
46
|
+
getProvinceById,
|
|
47
|
+
getCitiesByName,
|
|
43
48
|
getAllCities,
|
|
44
49
|
getProvinceOfCity,
|
|
45
50
|
searchProvinces,
|
|
@@ -49,15 +54,16 @@ import {
|
|
|
49
54
|
|
|
50
55
|
// همهی استانها به همراه شهرهایشان
|
|
51
56
|
getProvinces();
|
|
52
|
-
// [{ en: 'alborz', fa: 'البرز', cities: [{ en: 'karaj', fa: 'کرج' }, ...] }, ...]
|
|
57
|
+
// [{ id: 1, en: 'alborz', fa: 'البرز', cities: [{ id: 1, en: 'karaj', fa: 'کرج', slug: 'alborz/karaj' }, ...] }, ...]
|
|
53
58
|
|
|
54
59
|
// فقط لیست استانها (بدون شهر) - برای Select/Dropdown
|
|
55
60
|
getProvincesList();
|
|
56
|
-
// [{ en: 'alborz', fa: 'البرز' }, { en: 'ardabil', fa: 'اردبیل' }, ...]
|
|
61
|
+
// [{ id: 1, en: 'alborz', fa: 'البرز' }, { id: 2, en: 'ardabil', fa: 'اردبیل' }, ...]
|
|
57
62
|
|
|
58
63
|
// گرفتن یک استان با نام انگلیسی یا فارسی
|
|
59
64
|
getProvince('tehran');
|
|
60
65
|
getProvince('تهران'); // همین نتیجه را میدهد
|
|
66
|
+
getProvince(28); // با id عددی هم کار میکند (رشتهی عددی '28' هم قبول است)
|
|
61
67
|
|
|
62
68
|
// شهرهای یک استان خاص
|
|
63
69
|
getCities('fars'); // بر اساس شناسه انگلیسی
|
|
@@ -69,18 +75,33 @@ getCity('شیراز', 'fars'); // محدود به استان فارس
|
|
|
69
75
|
|
|
70
76
|
// لیست مسطح (flat) همهی شهرهای کشور همراه با استان مربوطه
|
|
71
77
|
getAllCities();
|
|
72
|
-
// [{ en: 'karaj', fa: 'کرج', provinceEn: 'alborz', provinceFa: 'البرز' }, ...]
|
|
78
|
+
// [{ id: 1, en: 'karaj', fa: 'کرج', slug: 'alborz/karaj', provinceId: 1, provinceEn: 'alborz', provinceFa: 'البرز' }, ...]
|
|
79
|
+
|
|
80
|
+
// گرفتن شهر/استان با id
|
|
81
|
+
getCityById(292); // => شیراز (provinceId: 7)
|
|
82
|
+
getProvinceById(7); // => فارس
|
|
83
|
+
|
|
84
|
+
// نامهای تکراری: همهی شهرهای با این نام
|
|
85
|
+
getCitiesByName('مهاباد'); // => [اصفهان/مهاباد (id 270), آذربایجان غربی/مهاباد (id 1171)]
|
|
73
86
|
|
|
74
87
|
// پیدا کردن استانِ یک شهر
|
|
75
|
-
getProvinceOfCity('اصفهان'); // => { en: 'isfahan', fa: 'اصفهان', cities: [...] }
|
|
88
|
+
getProvinceOfCity('اصفهان'); // => { id: 6, en: 'isfahan', fa: 'اصفهان', cities: [...] }
|
|
89
|
+
getProvinceOfCity(292); // با id شهر هم کار میکند
|
|
76
90
|
|
|
77
91
|
// جستجوی زنده (autocomplete) در استانها یا شهرها
|
|
78
92
|
searchProvinces('خراسان'); // هر سه استان خراسان را برمیگرداند
|
|
79
93
|
searchCities('شهر', { limit: 10 }); // ۱۰ نتیجهی اول شامل «شهر»
|
|
80
94
|
searchCities('mashhad', { locale: 'en' }); // جستجو فقط در نام انگلیسی
|
|
81
95
|
|
|
96
|
+
// مرکز استان، کد تلفن و مختصات
|
|
97
|
+
getCapital('khuzestan'); // => { id: 674, en: 'ahvaz', isCapital: true, coordinates: { lat: 31.31901, lng: 48.6842 }, phoneCode: '061', ... }
|
|
98
|
+
getCapitals(); // ۳۱ مرکز استان
|
|
99
|
+
getProvinceByPhoneCode('021'); // => تهران ('21' و 21 هم قبول است)
|
|
100
|
+
getCity('نجفآباد').phoneCode; // => '031' (کد استانِ شهر)
|
|
101
|
+
getCity('اهواز').coordinates; // => { lat, lng, confidence } | برای شهرهای غیرمرکز استان: undefined
|
|
102
|
+
|
|
82
103
|
// آمار کلی
|
|
83
|
-
getStats(); // { provinceCount: 31, cityCount:
|
|
104
|
+
getStats(); // { provinceCount: 31, cityCount: 1252 }
|
|
84
105
|
```
|
|
85
106
|
|
|
86
107
|
### نکتهی مهم دربارهی جستجو
|
|
@@ -119,21 +140,25 @@ app.get('/api/cities/search', (req, res) => {
|
|
|
119
140
|
import { useProvinces, useCities, useProvinceCitySelect, useCitySearch } from '@code-plate/iran-cities/react';
|
|
120
141
|
|
|
121
142
|
function AddressForm() {
|
|
122
|
-
const { provinces, cities, province, setProvince, city, setCity } =
|
|
143
|
+
const { provinces, cities, province, setProvince, city, setCity, provinceId, cityId, selectedCity } =
|
|
144
|
+
useProvinceCitySelect();
|
|
145
|
+
|
|
146
|
+
// موقع ارسال فرم: { provinceId, cityId } را به بکاند بفرستید
|
|
147
|
+
// selectedCity => { id, en, fa, slug, provinceId, provinceEn, provinceFa }
|
|
123
148
|
|
|
124
149
|
return (
|
|
125
150
|
<>
|
|
126
151
|
<select value={province} onChange={(e) => setProvince(e.target.value)}>
|
|
127
152
|
<option value="">انتخاب استان</option>
|
|
128
153
|
{provinces.map((p) => (
|
|
129
|
-
<option key={p.
|
|
154
|
+
<option key={p.id} value={p.en}>{p.fa}</option>
|
|
130
155
|
))}
|
|
131
156
|
</select>
|
|
132
157
|
|
|
133
158
|
<select value={city} onChange={(e) => setCity(e.target.value)} disabled={!province}>
|
|
134
159
|
<option value="">انتخاب شهر</option>
|
|
135
160
|
{cities.map((c) => (
|
|
136
|
-
<option key={c.
|
|
161
|
+
<option key={c.id} value={c.en}>{c.fa}</option>
|
|
137
162
|
))}
|
|
138
163
|
</select>
|
|
139
164
|
</>
|
|
@@ -154,7 +179,7 @@ function CityAutocomplete() {
|
|
|
154
179
|
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="نام شهر..." />
|
|
155
180
|
<ul>
|
|
156
181
|
{results.map((c) => (
|
|
157
|
-
<li key={
|
|
182
|
+
<li key={c.id}>{c.fa} ({c.provinceFa})</li>
|
|
158
183
|
))}
|
|
159
184
|
</ul>
|
|
160
185
|
</div>
|
|
@@ -168,7 +193,7 @@ function CityAutocomplete() {
|
|
|
168
193
|
|---|---|
|
|
169
194
|
| `useProvinces()` | لیست استانها |
|
|
170
195
|
| `useCities(provinceQuery)` | شهرهای یک استان، وابسته به تغییر پارامتر |
|
|
171
|
-
| `useProvinceCitySelect()` | state کامل یک فرم استان→شهر (با ریست خودکار شهر هنگام تغییر استان) |
|
|
196
|
+
| `useProvinceCitySelect()` | state کامل یک فرم استان→شهر (با ریست خودکار شهر هنگام تغییر استان)؛ علاوه بر `province`/`city` مقدارهای `provinceId`, `cityId`, `selectedProvince`, `selectedCity` و `reset()` را هم برمیگرداند |
|
|
172
197
|
| `useCitySearch(initialQuery?, options?)` | state جستجوی زندهی شهر |
|
|
173
198
|
| `useProvinceSearch(initialQuery?, options?)` | state جستجوی زندهی استان |
|
|
174
199
|
|
|
@@ -224,12 +249,18 @@ export class IranLocationsService {
|
|
|
224
249
|
| تابع | ورودی | خروجی | توضیح |
|
|
225
250
|
|---|---|---|---|
|
|
226
251
|
| `getProvinces()` | - | `Province[]` | همهی استانها به همراه شهرها |
|
|
227
|
-
| `getProvincesList()` | - | `{en, fa}[]` | فقط لیست استانها |
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
252
|
+
| `getProvincesList()` | - | `{id, en, fa}[]` | فقط لیست استانها |
|
|
253
|
+
| `getCapital(provinceQuery)` | `string \| number` | `FlatCity \| undefined` | مرکز استان |
|
|
254
|
+
| `getCapitals()` | - | `FlatCity[]` | مراکز ۳۱ استان |
|
|
255
|
+
| `getProvinceByPhoneCode(code)` | `string \| number` | `Province \| undefined` | استان با کد تلفن بینشهری |
|
|
256
|
+
| `getProvinceById(id)` | `number` | `Province \| undefined` | استان با id |
|
|
257
|
+
| `getCityById(id)` | `number` | `FlatCity \| undefined` | شهر با id سراسری |
|
|
258
|
+
| `getCitiesByName(name)` | `string` | `FlatCity[]` | همهی شهرهای با این نام (برای نامهای تکراری) |
|
|
259
|
+
| `getProvince(query)` | `string \| number` | `Province \| undefined` | یک استان با en یا fa |
|
|
260
|
+
| `getCities(provinceQuery)` | `string \| number` | `City[]` | شهرهای یک استان |
|
|
261
|
+
| `getCity(cityQuery, provinceQuery?)` | `string \| number, (string \| number)?` | `FlatCity \| undefined` | یک شهر مشخص |
|
|
231
262
|
| `getAllCities()` | - | `FlatCity[]` | همهی شهرها بهصورت مسطح |
|
|
232
|
-
| `getProvinceOfCity(cityQuery)` | `string` | `Province \| undefined` | استانِ یک شهر |
|
|
263
|
+
| `getProvinceOfCity(cityQuery)` | `string \| number` | `Province \| undefined` | استانِ یک شهر |
|
|
233
264
|
| `searchProvinces(query, options?)` | `string, SearchOptions?` | `Province[]` | جستجوی fuzzy استان |
|
|
234
265
|
| `searchCities(query, options?)` | `string, SearchOptions?` | `FlatCity[]` | جستجوی fuzzy شهر |
|
|
235
266
|
| `getStats()` | - | `{provinceCount, cityCount}` | آمار کلی |
|
|
@@ -239,9 +270,10 @@ export class IranLocationsService {
|
|
|
239
270
|
### تایپها
|
|
240
271
|
|
|
241
272
|
```ts
|
|
242
|
-
interface
|
|
243
|
-
interface
|
|
244
|
-
interface
|
|
273
|
+
interface Coordinates { lat: number; lng: number; confidence: 'high' | 'medium' | 'low'; }
|
|
274
|
+
interface City { id: number; isCapital: boolean; coordinates?: Coordinates; slug: string; en: string; fa: string; }
|
|
275
|
+
interface Province { id: number; en: string; fa: string; phoneCode: string; capitalId: number; cities: City[]; }
|
|
276
|
+
interface FlatCity extends City { provinceId: number; provinceEn: string; provinceFa: string; phoneCode: string; }
|
|
245
277
|
```
|
|
246
278
|
|
|
247
279
|
---
|
|
@@ -252,8 +284,12 @@ interface FlatCity extends City { provinceEn: string; provinceFa: string; }
|
|
|
252
284
|
npm install
|
|
253
285
|
npm run build # خروجی dist/ (cjs + esm + d.ts)
|
|
254
286
|
npm run typecheck
|
|
287
|
+
npm run validate # اعتبارسنجی یکپارچگی data.json (id، slug، مرکز استان، کد تلفن، مختصات)
|
|
288
|
+
npm test # validate + typecheck
|
|
255
289
|
```
|
|
256
290
|
|
|
291
|
+
> **مختصات:** برای ۱۱۵۲ شهر از ۱۲۵۲ شهر موجود است (`coordinates: { lat, lng, confidence }`). `confidence` سه مقدار دارد: `'high'` (۹۷۷ شهر) یعنی منبع مستقل دیگری هم همان نقطه را تأیید کرده؛ `'medium'` (۹۸ شهر) یعنی نام شهر در GeoNames یکتا و شهر واقعی بوده ولی تأیید مستقل نداشته؛ `'low'` (۷۷ شهر) یعنی فقط تطبیق نام در GeoNames هست و مکانِ مطابق کوچک یا بدون جمعیت ثبت شده یا نام تقریبی تطبیق داده شده (فقط برای نمایش تقریبی؛ خطای چند تا دهها کیلومتر محتمل است). برای ۱۰۰ شهر باقیمانده `coordinates` برابر `undefined` است و هیچ مقدار تقریبی جایگزین نمیشود (فهرست و دلیل: `reports/coordinates-not-imported.csv`). منبع داده: GeoNames (CC BY 4.0)، فایل `NOTICE.md`.
|
|
292
|
+
|
|
257
293
|
## لایسنس
|
|
258
294
|
|
|
259
295
|
MIT
|
package/README.md
CHANGED
|
@@ -26,8 +26,10 @@ pnpm add @code-plate/iran-cities
|
|
|
26
26
|
## Data
|
|
27
27
|
|
|
28
28
|
- `31` provinces
|
|
29
|
-
- `
|
|
30
|
-
- Every record has an `en` field (latin, kebab-case identifier, e.g. `tehran`) and an `fa` field (Persian name, e.g. `تهران`).
|
|
29
|
+
- `1252` cities
|
|
30
|
+
- Every record has an `id` (stable numeric identifier), an `en` field (latin, kebab-case identifier, e.g. `tehran`) and an `fa` field (Persian name, e.g. `تهران`).
|
|
31
|
+
- Every province has a `phoneCode` (area code) and a `capitalId`; every city has `isCapital`, and province capitals also carry `coordinates: { lat, lng }`.
|
|
32
|
+
- City `id`s are **globally unique** (same-named cities such as «Mahabad» in two provinces get different ids) and never change between versions; new cities always get the next id.
|
|
31
33
|
|
|
32
34
|
---
|
|
33
35
|
|
|
@@ -40,6 +42,9 @@ import {
|
|
|
40
42
|
getProvince,
|
|
41
43
|
getCities,
|
|
42
44
|
getCity,
|
|
45
|
+
getCityById,
|
|
46
|
+
getProvinceById,
|
|
47
|
+
getCitiesByName,
|
|
43
48
|
getAllCities,
|
|
44
49
|
getProvinceOfCity,
|
|
45
50
|
searchProvinces,
|
|
@@ -49,15 +54,16 @@ import {
|
|
|
49
54
|
|
|
50
55
|
// All provinces along with their cities
|
|
51
56
|
getProvinces();
|
|
52
|
-
// [{ en: 'alborz', fa: 'البرز', cities: [{ en: 'karaj', fa: 'کرج' }, ...] }, ...]
|
|
57
|
+
// [{ id: 1, en: 'alborz', fa: 'البرز', cities: [{ id: 1, en: 'karaj', fa: 'کرج', slug: 'alborz/karaj' }, ...] }, ...]
|
|
53
58
|
|
|
54
59
|
// Just the list of provinces (no cities) - for a Select/Dropdown
|
|
55
60
|
getProvincesList();
|
|
56
|
-
// [{ en: 'alborz', fa: 'البرز' }, { en: 'ardabil', fa: 'اردبیل' }, ...]
|
|
61
|
+
// [{ id: 1, en: 'alborz', fa: 'البرز' }, { id: 2, en: 'ardabil', fa: 'اردبیل' }, ...]
|
|
57
62
|
|
|
58
63
|
// Get a single province by English id or Persian name
|
|
59
64
|
getProvince('tehran');
|
|
60
65
|
getProvince('تهران'); // same result
|
|
66
|
+
getProvince(28); // numeric id works too (so does the string '28')
|
|
61
67
|
|
|
62
68
|
// Cities of a specific province
|
|
63
69
|
getCities('fars'); // by English id
|
|
@@ -69,18 +75,33 @@ getCity('شیراز', 'fars'); // scoped to Fars province
|
|
|
69
75
|
|
|
70
76
|
// Flat list of every city in the country, along with its province
|
|
71
77
|
getAllCities();
|
|
72
|
-
// [{ en: 'karaj', fa: 'کرج', provinceEn: 'alborz', provinceFa: 'البرز' }, ...]
|
|
78
|
+
// [{ id: 1, en: 'karaj', fa: 'کرج', slug: 'alborz/karaj', provinceId: 1, provinceEn: 'alborz', provinceFa: 'البرز' }, ...]
|
|
79
|
+
|
|
80
|
+
// Look up by id
|
|
81
|
+
getCityById(292); // => Shiraz (provinceId: 7)
|
|
82
|
+
getProvinceById(7); // => Fars
|
|
83
|
+
|
|
84
|
+
// Duplicate names: every city with that name
|
|
85
|
+
getCitiesByName('مهاباد'); // => [Isfahan/Mahabad (id 270), West Azerbaijan/Mahabad (id 1171)]
|
|
73
86
|
|
|
74
87
|
// Find which province a city belongs to
|
|
75
|
-
getProvinceOfCity('اصفهان'); // => { en: 'isfahan', fa: 'اصفهان', cities: [...] }
|
|
88
|
+
getProvinceOfCity('اصفهان'); // => { id: 6, en: 'isfahan', fa: 'اصفهان', cities: [...] }
|
|
89
|
+
getProvinceOfCity(292); // city id works too
|
|
76
90
|
|
|
77
91
|
// Live search (autocomplete) over provinces or cities
|
|
78
92
|
searchProvinces('خراسان'); // returns all three "Khorasan" provinces
|
|
79
93
|
searchCities('شهر', { limit: 10 }); // first 10 results containing "شهر"
|
|
80
94
|
searchCities('mashhad', { locale: 'en' }); // search English names only
|
|
81
95
|
|
|
96
|
+
// Capital, area code and coordinates
|
|
97
|
+
getCapital('khuzestan'); // => { id: 674, en: 'ahvaz', isCapital: true, coordinates: { lat: 31.31901, lng: 48.6842 }, phoneCode: '061', ... }
|
|
98
|
+
getCapitals(); // the 31 provincial capitals
|
|
99
|
+
getProvinceByPhoneCode('021'); // => Tehran ('21' and 21 work too)
|
|
100
|
+
getCity('نجفآباد').phoneCode; // => '031' (area code of the city's province)
|
|
101
|
+
getCity('اهواز').coordinates; // => { lat, lng, confidence } | undefined for non-capital cities
|
|
102
|
+
|
|
82
103
|
// Overall stats
|
|
83
|
-
getStats(); // { provinceCount: 31, cityCount:
|
|
104
|
+
getStats(); // { provinceCount: 31, cityCount: 1252 }
|
|
84
105
|
```
|
|
85
106
|
|
|
86
107
|
### A note on search
|
|
@@ -119,21 +140,25 @@ An optional subpath is available for React with a few ready-made hooks:
|
|
|
119
140
|
import { useProvinces, useCities, useProvinceCitySelect, useCitySearch } from '@code-plate/iran-cities/react';
|
|
120
141
|
|
|
121
142
|
function AddressForm() {
|
|
122
|
-
const { provinces, cities, province, setProvince, city, setCity } =
|
|
143
|
+
const { provinces, cities, province, setProvince, city, setCity, provinceId, cityId, selectedCity } =
|
|
144
|
+
useProvinceCitySelect();
|
|
145
|
+
|
|
146
|
+
// On submit, send { provinceId, cityId } to your backend.
|
|
147
|
+
// selectedCity => { id, en, fa, slug, provinceId, provinceEn, provinceFa }
|
|
123
148
|
|
|
124
149
|
return (
|
|
125
150
|
<>
|
|
126
151
|
<select value={province} onChange={(e) => setProvince(e.target.value)}>
|
|
127
152
|
<option value="">Select a province</option>
|
|
128
153
|
{provinces.map((p) => (
|
|
129
|
-
<option key={p.
|
|
154
|
+
<option key={p.id} value={p.en}>{p.fa}</option>
|
|
130
155
|
))}
|
|
131
156
|
</select>
|
|
132
157
|
|
|
133
158
|
<select value={city} onChange={(e) => setCity(e.target.value)} disabled={!province}>
|
|
134
159
|
<option value="">Select a city</option>
|
|
135
160
|
{cities.map((c) => (
|
|
136
|
-
<option key={c.
|
|
161
|
+
<option key={c.id} value={c.en}>{c.fa}</option>
|
|
137
162
|
))}
|
|
138
163
|
</select>
|
|
139
164
|
</>
|
|
@@ -154,7 +179,7 @@ function CityAutocomplete() {
|
|
|
154
179
|
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="City name..." />
|
|
155
180
|
<ul>
|
|
156
181
|
{results.map((c) => (
|
|
157
|
-
<li key={
|
|
182
|
+
<li key={c.id}>{c.fa} ({c.provinceFa})</li>
|
|
158
183
|
))}
|
|
159
184
|
</ul>
|
|
160
185
|
</div>
|
|
@@ -168,7 +193,7 @@ Hooks available in the `react` subpath:
|
|
|
168
193
|
|---|---|
|
|
169
194
|
| `useProvinces()` | List of provinces |
|
|
170
195
|
| `useCities(provinceQuery)` | Cities of a province, reacts to changes in the parameter |
|
|
171
|
-
| `useProvinceCitySelect()` | Full state + logic for a province→city form field (auto-resets the city when the province changes) |
|
|
196
|
+
| `useProvinceCitySelect()` | Full state + logic for a province→city form field (auto-resets the city when the province changes); also returns `provinceId`, `cityId`, `selectedProvince`, `selectedCity` and `reset()` |
|
|
172
197
|
| `useCitySearch(initialQuery?, options?)` | Live city search state |
|
|
173
198
|
| `useProvinceSearch(initialQuery?, options?)` | Live province search state |
|
|
174
199
|
|
|
@@ -224,12 +249,18 @@ export class IranLocationsService {
|
|
|
224
249
|
| Function | Input | Output | Description |
|
|
225
250
|
|---|---|---|---|
|
|
226
251
|
| `getProvinces()` | - | `Province[]` | All provinces with their cities |
|
|
227
|
-
| `getProvincesList()` | - | `{en, fa}[]` | Provinces only |
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
252
|
+
| `getProvincesList()` | - | `{id, en, fa}[]` | Provinces only |
|
|
253
|
+
| `getCapital(provinceQuery)` | `string \| number` | `FlatCity \| undefined` | Capital of a province |
|
|
254
|
+
| `getCapitals()` | - | `FlatCity[]` | The 31 provincial capitals |
|
|
255
|
+
| `getProvinceByPhoneCode(code)` | `string \| number` | `Province \| undefined` | Province by area code |
|
|
256
|
+
| `getProvinceById(id)` | `number` | `Province \| undefined` | Province by id |
|
|
257
|
+
| `getCityById(id)` | `number` | `FlatCity \| undefined` | City by global id |
|
|
258
|
+
| `getCitiesByName(name)` | `string` | `FlatCity[]` | Every city with that name (for duplicate names) |
|
|
259
|
+
| `getProvince(query)` | `string \| number` | `Province \| undefined` | A province by en or fa |
|
|
260
|
+
| `getCities(provinceQuery)` | `string \| number` | `City[]` | Cities of a province |
|
|
261
|
+
| `getCity(cityQuery, provinceQuery?)` | `string \| number, (string \| number)?` | `FlatCity \| undefined` | A specific city |
|
|
231
262
|
| `getAllCities()` | - | `FlatCity[]` | All cities, flattened |
|
|
232
|
-
| `getProvinceOfCity(cityQuery)` | `string` | `Province \| undefined` | The province a city belongs to |
|
|
263
|
+
| `getProvinceOfCity(cityQuery)` | `string \| number` | `Province \| undefined` | The province a city belongs to |
|
|
233
264
|
| `searchProvinces(query, options?)` | `string, SearchOptions?` | `Province[]` | Fuzzy province search |
|
|
234
265
|
| `searchCities(query, options?)` | `string, SearchOptions?` | `FlatCity[]` | Fuzzy city search |
|
|
235
266
|
| `getStats()` | - | `{provinceCount, cityCount}` | Overall stats |
|
|
@@ -239,9 +270,10 @@ export class IranLocationsService {
|
|
|
239
270
|
### Types
|
|
240
271
|
|
|
241
272
|
```ts
|
|
242
|
-
interface
|
|
243
|
-
interface
|
|
244
|
-
interface
|
|
273
|
+
interface Coordinates { lat: number; lng: number; confidence: 'high' | 'medium' | 'low'; }
|
|
274
|
+
interface City { id: number; isCapital: boolean; coordinates?: Coordinates; slug: string; en: string; fa: string; }
|
|
275
|
+
interface Province { id: number; en: string; fa: string; phoneCode: string; capitalId: number; cities: City[]; }
|
|
276
|
+
interface FlatCity extends City { provinceId: number; provinceEn: string; provinceFa: string; phoneCode: string; }
|
|
245
277
|
```
|
|
246
278
|
|
|
247
279
|
---
|
|
@@ -252,8 +284,12 @@ interface FlatCity extends City { provinceEn: string; provinceFa: string; }
|
|
|
252
284
|
npm install
|
|
253
285
|
npm run build # outputs dist/ (cjs + esm + d.ts)
|
|
254
286
|
npm run typecheck
|
|
287
|
+
npm run validate # data integrity checks for data.json (ids, slugs, capitals, area codes, coordinates)
|
|
288
|
+
npm test # validate + typecheck
|
|
255
289
|
```
|
|
256
290
|
|
|
291
|
+
> **Coordinates:** available for 1152 of 1252 cities (`coordinates: { lat, lng, confidence }`). `confidence` has three levels: `'high'` (977 cities) – an independent source confirms the same point; `'medium'` (98) – the name is unique in GeoNames and the place is city-like, but no independent confirmation; `'low'` (77) – only a name match in GeoNames, where the matched place is small/unpopulated or the name match was approximate (approximate display only; errors of several to tens of km are possible). For the remaining 100 cities `coordinates` is `undefined`; no approximate values are filled in (list and reasons: `reports/coordinates-not-imported.csv`). Data source: GeoNames (CC BY 4.0), see `NOTICE.md`.
|
|
292
|
+
|
|
257
293
|
## License
|
|
258
294
|
|
|
259
295
|
MIT
|