@zukall/zap 0.0.0-stage → 0.2.1
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/LICENSE +21 -0
- package/PROTOCOL-V1.md +117 -0
- package/PROTOCOL.md +176 -0
- package/README.md +554 -2
- package/THIRD-PARTY-NOTICES.md +19 -0
- package/asset/zukall-diagnol-example.webp +0 -0
- package/asset/zukall-north-south-example.webp +0 -0
- package/asset/zukall-west-east.webp +0 -0
- package/dist/cjs/address.d.ts +3 -0
- package/dist/cjs/address.js +38 -0
- package/dist/cjs/address_bounty.d.ts +26 -0
- package/dist/cjs/address_bounty.js +2334 -0
- package/dist/cjs/address_province_bounty.d.ts +56 -0
- package/dist/cjs/address_province_bounty.js +5563 -0
- package/dist/cjs/country.d.ts +6 -0
- package/dist/cjs/country.js +27 -0
- package/dist/cjs/data.d.ts +3 -0
- package/dist/cjs/data.js +33 -0
- package/dist/cjs/directions.d.ts +5 -0
- package/dist/cjs/directions.js +13 -0
- package/dist/cjs/encoding.d.ts +3 -0
- package/dist/cjs/encoding.js +20 -0
- package/dist/cjs/grid.d.ts +23 -0
- package/dist/cjs/grid.js +152 -0
- package/dist/cjs/index.d.ts +27 -0
- package/dist/cjs/index.js +26 -0
- package/dist/cjs/internal.d.ts +3 -0
- package/dist/cjs/internal.js +19 -0
- package/dist/cjs/legacy-address.d.ts +3 -0
- package/dist/cjs/legacy-address.js +44 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/protocol.d.ts +22 -0
- package/dist/cjs/protocol.js +25 -0
- package/dist/cjs/types.d.ts +114 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/address.d.ts +3 -0
- package/dist/esm/address.js +35 -0
- package/dist/esm/address_bounty.d.ts +26 -0
- package/dist/esm/address_bounty.js +2331 -0
- package/dist/esm/address_province_bounty.d.ts +56 -0
- package/dist/esm/address_province_bounty.js +5560 -0
- package/dist/esm/country.d.ts +6 -0
- package/dist/esm/country.js +22 -0
- package/dist/esm/data.d.ts +3 -0
- package/dist/esm/data.js +2 -0
- package/dist/esm/directions.d.ts +5 -0
- package/dist/esm/directions.js +10 -0
- package/dist/esm/encoding.d.ts +3 -0
- package/dist/esm/encoding.js +17 -0
- package/dist/esm/grid.d.ts +23 -0
- package/dist/esm/grid.js +148 -0
- package/dist/esm/index.d.ts +27 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/internal.d.ts +3 -0
- package/dist/esm/internal.js +15 -0
- package/dist/esm/legacy-address.d.ts +3 -0
- package/dist/esm/legacy-address.js +41 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/protocol.d.ts +22 -0
- package/dist/esm/protocol.js +22 -0
- package/dist/esm/types.d.ts +114 -0
- package/dist/esm/types.js +1 -0
- package/package.json +68 -4
package/README.md
CHANGED
|
@@ -1,3 +1,555 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ZAP — Zukall Addressing Protocol
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**`@zukall/zap`** generates addresses directly from latitude and longitude using
|
|
4
|
+
fixed country/province grids. Protocol **2** encodes **100-meter blocks** and
|
|
5
|
+
separate **whole-meter remainders**. It needs no Google Places API, geocoding
|
|
6
|
+
service, network request, or runtime dependency. Generation works offline.
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
1194 S 382 E #16-20, Kampala, UG ZAP
|
|
10
|
+
2106 S 3614 E #22-64, Miami, FL US ZAP
|
|
11
|
+
580 S 770 E #43-46, HT ZAP
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
These illustrate the display format. Actual numbers depend on the original GPS
|
|
15
|
+
point and selected grid; a city label never changes the origin or verifies
|
|
16
|
+
administrative membership.
|
|
17
|
+
|
|
18
|
+
## Why use ZAP?
|
|
19
|
+
|
|
20
|
+
**Different countries. Different street addresses. One consistent location format.**
|
|
21
|
+
|
|
22
|
+
A well-defined address identifies a destination, but it does not automatically tell an unfamiliar person how to reach it. Immokalee, Florida, has formally assigned addresses, yet a newcomer may still need a map, street signs, or knowledge of the road layout.
|
|
23
|
+
|
|
24
|
+
ZAP gives your application a consistent way to address locations directly from GPS coordinates. It works alongside conventional addresses and can provide a location address where street names or building numbers are missing.
|
|
25
|
+
|
|
26
|
+
### Addresses without waiting for an address authority
|
|
27
|
+
|
|
28
|
+
ZAP derives its location code from coordinates and a fixed grid. A country does
|
|
29
|
+
not need an address authority, a complete street-address registry, named roads,
|
|
30
|
+
or assigned building numbers for applications to generate ZAP addresses within
|
|
31
|
+
that grid. A home, business, rural property entrance, or pickup point can receive
|
|
32
|
+
a coordinate address as soon as its GPS location is available.
|
|
33
|
+
|
|
34
|
+
The same method can be applied in any country with a configured grid. This
|
|
35
|
+
release includes **239 country/territory configurations**, with country-wide
|
|
36
|
+
grids or explicitly selected province/state grids. Coverage follows those
|
|
37
|
+
configurations; adding an unconfigured country requires a versioned grid
|
|
38
|
+
definition. The address structure stays consistent across them.
|
|
39
|
+
|
|
40
|
+
**Haiti is the illustrated example below.** Its `HT` configuration provides one
|
|
41
|
+
fixed country grid, so a place can be addressed even when its street name,
|
|
42
|
+
building number, or formal address record is unavailable. This shows independence
|
|
43
|
+
from official address assignment; the method also works alongside existing
|
|
44
|
+
Haitian street addresses, such as the hotel example in the table.
|
|
45
|
+
|
|
46
|
+
A visitor or delivery driver can use a ZAP-aware application to view their
|
|
47
|
+
current location and destination in the same grid. Comparing their blocks,
|
|
48
|
+
directions, and remainders helps them understand the destination's relative
|
|
49
|
+
position. Saved GPS coordinates connect that destination to road directions.
|
|
50
|
+
|
|
51
|
+
### Different local addresses, the same ZAP structure
|
|
52
|
+
|
|
53
|
+
| Location | Conventional address | ZAP address |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| **Miami, USA — City Hall** | 3500 Pan American Drive, Miami, FL 33133, USA. | `2220 S 3602 E #92-35, Miami, FL US ZAP` |
|
|
56
|
+
| **Beijing, China — Sofitel Beijing Central** | No. 2 Jianguomen South Avenue, Chaoyang District, Beijing 100022, China. | `375 S 15 W #62-74, Beijing, BJ CN ZAP` |
|
|
57
|
+
| **Abidjan, Côte d’Ivoire — Sofitel Hotel Ivoire** | Boulevard Hassan II, Cocody, 08 BP 01 Abidjan, Côte d’Ivoire. | `2452 S 1724 E #88-29, Abidjan, CI ZAP` |
|
|
58
|
+
| **Port-au-Prince, Haiti — Marriott Hotel** | 147 Avenue Jean Paul II, Turgeau, Port-au-Prince, HT 6113, Haiti. | `582 S 780 E #29-13, Port-au-Prince, HT ZAP` |
|
|
59
|
+
| **Berlin, Germany — Brandenburg Gate** | Pariser Platz 5, 10117 Berlin, Germany. | `1538 N 1989 E #91-52, Berlin, DE ZAP` |
|
|
60
|
+
|
|
61
|
+
*These examples encode the venues’ published map/GPS points using package version 0.2.0. They demonstrate the format; the points are not independently verified entrances.*
|
|
62
|
+
|
|
63
|
+
Street names, languages, address order, and postal components vary. ZAP uses the same generation function and encoding structure across its configured grids.
|
|
64
|
+
|
|
65
|
+
### Understand the address
|
|
66
|
+
|
|
67
|
+
In `582 S 780 E #29-13, Port-au-Prince, HT ZAP`:
|
|
68
|
+
|
|
69
|
+
- **582 S:** 582 complete 100-meter blocks south of the selected grid origin.
|
|
70
|
+
- **780 E:** 780 complete 100-meter blocks east of that origin.
|
|
71
|
+
- **#29-13:** another 29 whole projected meters south and 13 east within those blocks.
|
|
72
|
+
- **HT:** Haiti’s country code.
|
|
73
|
+
|
|
74
|
+
The directions refer to the grid’s fixed origin. The suffix describes coordinate detail, rather than a sequential house number.
|
|
75
|
+
|
|
76
|
+
### Why developers choose ZAP
|
|
77
|
+
|
|
78
|
+
- **Generate addresses from GPS**, including locations without useful street addresses.
|
|
79
|
+
- **Use one format across configured countries and provinces.**
|
|
80
|
+
- **Generate offline**, without a geocoding API, API key, or runtime dependency once coordinates are available.
|
|
81
|
+
- **Get repeatable results** from the same coordinates, grid, and protocol version.
|
|
82
|
+
- **Keep local context**, displaying street addresses, landmarks, and unit details alongside ZAP.
|
|
83
|
+
|
|
84
|
+
### Identifying a destination and reaching it
|
|
85
|
+
|
|
86
|
+
ZAP works with straight, diagonal, and curved streets. Its grid stays fixed; both coordinate numbers can change along a diagonal road.
|
|
87
|
+
|
|
88
|
+
For navigation, use the original saved GPS coordinates—preferably the entrance point—to open road directions. Keep helpful street names and landmarks visible.
|
|
89
|
+
|
|
90
|
+
ZAP complements existing addresses. Whole-meter encoding does not guarantee one-meter GPS accuracy, and a location code does not automatically identify a unique household or apartment.
|
|
91
|
+
|
|
92
|
+
## See the address grid: Haiti illustrations
|
|
93
|
+
|
|
94
|
+
These illustrations show locations in the same fixed Haiti grid. Each address
|
|
95
|
+
is derived independently from its coordinates. The road can run north–south,
|
|
96
|
+
east–west, diagonally, or curve; the grid origin and axes stay fixed.
|
|
97
|
+
|
|
98
|
+
**Current suffix notation is `#42-68`.** The north–south and east–west images use
|
|
99
|
+
the compact notation `#4268` for the same two remainders: 42 south and 68 east.
|
|
100
|
+
The examples and captions below use the current dashed format.
|
|
101
|
+
|
|
102
|
+
### North–south: easting stays fixed
|
|
103
|
+
|
|
104
|
+

|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
Point A: 580 S 772 E #42-68, HT ZAP
|
|
108
|
+
Point B: 581 S 772 E #42-68, HT ZAP
|
|
109
|
+
Point C: 582 S 772 E #42-68, HT ZAP
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
From A to B, the location moves 100 projected meters south: the south block
|
|
113
|
+
increases from 580 to 581, while the east block and both remainders stay fixed.
|
|
114
|
+
Moving north on this side of the origin decreases the south block.
|
|
115
|
+
|
|
116
|
+
### East–west: northing stays fixed
|
|
117
|
+
|
|
118
|
+

|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
Point A: 582 S 770 E #42-68, HT ZAP
|
|
122
|
+
Point B: 582 S 771 E #42-68, HT ZAP
|
|
123
|
+
Point C: 582 S 772 E #42-68, HT ZAP
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
From A to B, the location moves 100 projected meters east: the east block
|
|
127
|
+
increases from 770 to 771, while the south block and both remainders stay fixed.
|
|
128
|
+
Moving west on this side of the origin decreases the east block.
|
|
129
|
+
|
|
130
|
+
### Diagonal streets: both axes can change
|
|
131
|
+
|
|
132
|
+

|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
Point A: 580 S 770 E #43-46, HT ZAP
|
|
136
|
+
Point B: 581 S 771 E #12-36, HT ZAP
|
|
137
|
+
Point C: 582 S 772 E #08-41, HT ZAP
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Both axes and their remainders can change along a diagonal or curved street.
|
|
141
|
+
For point C, the whole-meter magnitudes are `582 × 100 + 08 = 58,208` south
|
|
142
|
+
and `772 × 100 + 41 = 77,241` east. The `#08-41` suffix keeps its leading zero.
|
|
143
|
+
|
|
144
|
+
The images are illustrative grid examples. To reach a real place, use its saved
|
|
145
|
+
GPS entrance point for road directions; the grid comparison describes relative
|
|
146
|
+
position rather than a route through buildings or other obstacles. The same
|
|
147
|
+
encoding applies to the other configured countries and provinces.
|
|
148
|
+
|
|
149
|
+
The README uses three compressed WebP illustrations, each 95–98 KB and 1280
|
|
150
|
+
pixels wide. Original PNGs remain in the source project; only the smaller WebP
|
|
151
|
+
versions are included in the npm package. The compressed package stays below
|
|
152
|
+
400 KB, including all three illustrations.
|
|
153
|
+
|
|
154
|
+
## Installation and local verification
|
|
155
|
+
|
|
156
|
+
After publication:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
npm install @zukall/zap
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Before publication, install the verified local package:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
npm install /absolute/path/to/zukall-address-protocol/.artifacts/zukall-zap-0.2.1.tgz
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Build and verify the source:
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
cd /absolute/path/to/zukall-address-protocol
|
|
172
|
+
npm ci
|
|
173
|
+
npm run check
|
|
174
|
+
npm run pack:check
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Node.js 20 or newer is required for development and Node consumers. Browser
|
|
178
|
+
bundlers can use the same generator; runtime code has no Node-specific APIs.
|
|
179
|
+
ESM, CommonJS and TypeScript declarations are included. `pack:check` installs the
|
|
180
|
+
tarball into a temporary consumer and verifies both module formats and types.
|
|
181
|
+
|
|
182
|
+
## Quick start: ESM
|
|
183
|
+
|
|
184
|
+
```js
|
|
185
|
+
import { generateAddress } from "@zukall/zap";
|
|
186
|
+
|
|
187
|
+
const result = generateAddress({
|
|
188
|
+
countryCode: "UG",
|
|
189
|
+
city: "Kampala",
|
|
190
|
+
latitude: 0.3476,
|
|
191
|
+
longitude: 32.5825,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
console.log(result.address); // 1139 S 339 E #33-19, Kampala, UG ZAP
|
|
195
|
+
console.log(result.protocolVersion); // 2 (string)
|
|
196
|
+
console.log(result.northSouthBlock); // 1139
|
|
197
|
+
console.log(result.eastWestBlock); // 339
|
|
198
|
+
console.log(result.northSouthRemainder); // 33
|
|
199
|
+
console.log(result.eastWestRemainder); // 19
|
|
200
|
+
console.log(result.coordinateSuffix); // #33-19
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The complete result is:
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"protocol": "ZAP",
|
|
208
|
+
"protocolVersion": "2",
|
|
209
|
+
"address": "1139 S 339 E #33-19, Kampala, UG ZAP",
|
|
210
|
+
"countryCode": "UG",
|
|
211
|
+
"gridName": "Uganda",
|
|
212
|
+
"countryVersion": "ne-admin0-239eec57ac17-v1",
|
|
213
|
+
"gridVersion": "ne-admin0-239eec57ac17-v1",
|
|
214
|
+
"coordinates": {
|
|
215
|
+
"latitude": 0.3476,
|
|
216
|
+
"longitude": 32.5825
|
|
217
|
+
},
|
|
218
|
+
"center": {
|
|
219
|
+
"latitude": 1.372243,
|
|
220
|
+
"longitude": 32.277466499999946
|
|
221
|
+
},
|
|
222
|
+
"widthMeters": 606730,
|
|
223
|
+
"heightMeters": 633245,
|
|
224
|
+
"northingMeters": -113933.63641380174,
|
|
225
|
+
"eastingMeters": 33919.40824532269,
|
|
226
|
+
"city": "Kampala",
|
|
227
|
+
"northSouthBlock": 1139,
|
|
228
|
+
"eastWestBlock": 339,
|
|
229
|
+
"northSouthDirection": "S",
|
|
230
|
+
"eastWestDirection": "E",
|
|
231
|
+
"northSouthRemainder": 33,
|
|
232
|
+
"eastWestRemainder": 19,
|
|
233
|
+
"coordinateSuffix": "#33-19",
|
|
234
|
+
"blockSizeMeters": 100,
|
|
235
|
+
"detailSizeMeters": 1,
|
|
236
|
+
"encoding": "blocks-100m-detail-1m-floor-absolute-dashed-suffix-2x2-v1",
|
|
237
|
+
"gridId": "ZAP:2:UG:ne-admin0-239eec57ac17-v1:ne-admin0-239eec57ac17-v1:AEQD-SPHERE-v1:6371008.8:1.372243:32.277466499999946:blocks-100m-detail-1m-floor-absolute-dashed-suffix-2x2-v1"
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`coordinates` retains the original GPS point. `northingMeters` and `eastingMeters`
|
|
242
|
+
are signed, **unrounded** projected offsets. Remainders are numbers for application
|
|
243
|
+
use; `coordinateSuffix` and `address` always format them with two digits.
|
|
244
|
+
`widthMeters` and `heightMeters` are approximate rectangle dimensions rounded to
|
|
245
|
+
whole meters.
|
|
246
|
+
|
|
247
|
+
You may also pass an optional `street` label. It is returned as separate metadata
|
|
248
|
+
and does not enter the formatted coordinate address or affect the grid. City and
|
|
249
|
+
street labels are trimmed, whitespace is normalized, and blank labels are omitted.
|
|
250
|
+
|
|
251
|
+
## CommonJS and selected provinces
|
|
252
|
+
|
|
253
|
+
```js
|
|
254
|
+
const { generateAddress, ZAP_PROTOCOL } = require("@zukall/zap");
|
|
255
|
+
|
|
256
|
+
const result = generateAddress({
|
|
257
|
+
countryCode: "US",
|
|
258
|
+
provinceCode: "FL",
|
|
259
|
+
city: "Miami",
|
|
260
|
+
latitude: 25.7617,
|
|
261
|
+
longitude: -80.1918,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
console.log(ZAP_PROTOCOL.name); // Zukall Addressing Protocol
|
|
265
|
+
console.log(result.address); // 2181 S 3643 E #89-31, Miami, FL US ZAP
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Countries with province/state grids require the caller to choose one. Countries
|
|
269
|
+
with country-wide grids require only the country code and reject province codes.
|
|
270
|
+
Existing corners, origins, snapshot versions and overlapping rectangles are
|
|
271
|
+
unchanged. Rectangle containment does not establish actual administrative
|
|
272
|
+
membership. City and street labels do not determine the selected grid.
|
|
273
|
+
|
|
274
|
+
## TypeScript and the named namespace
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import {
|
|
278
|
+
generateAddress,
|
|
279
|
+
zap,
|
|
280
|
+
type AddressInput,
|
|
281
|
+
type AddressResult,
|
|
282
|
+
} from "@zukall/zap";
|
|
283
|
+
|
|
284
|
+
const input: AddressInput = {
|
|
285
|
+
countryCode: "US",
|
|
286
|
+
provinceCode: "AK",
|
|
287
|
+
latitude: 61.2181,
|
|
288
|
+
longitude: -149.9003,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const result: AddressResult = generateAddress(input);
|
|
292
|
+
console.log(result.address); // 214 N 4726 E #44-94, AK US ZAP
|
|
293
|
+
console.log(zap.generateAddress(input).address); // Same address
|
|
294
|
+
console.log(zap.protocol.version); // 2
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Public types include `AddressInput`, `AddressResult`, `AddressEncoding`,
|
|
298
|
+
`LegacyAddressInput`, `LegacyAddressResult`, `GridInput`, `Coordinates`, `Corners`,
|
|
299
|
+
`Country`, `Province`, `ProvinceCountry`, `CountryOption`, and `ProvinceOption`.
|
|
300
|
+
ESM and CommonJS consumers receive matching declarations.
|
|
301
|
+
|
|
302
|
+
## Display formatting and the encoding
|
|
303
|
+
|
|
304
|
+
The format is:
|
|
305
|
+
|
|
306
|
+
```text
|
|
307
|
+
{northSouthBlock} {N|S} {eastWestBlock} {E|W} #{northSouthRemainder}-{eastWestRemainder}, {city}, {provinceCode} {countryCode} ZAP
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Missing city/province components are omitted cleanly. The final province,
|
|
311
|
+
country and `ZAP` labels are separated by spaces:
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
const withoutCity = generateAddress({
|
|
315
|
+
countryCode: "UG", latitude: 0.3476, longitude: 32.5825,
|
|
316
|
+
});
|
|
317
|
+
console.log(withoutCity.address); // 1139 S 339 E #33-19, UG ZAP
|
|
318
|
+
|
|
319
|
+
const withoutCityInProvince = generateAddress({
|
|
320
|
+
countryCode: "US", provinceCode: "CA", latitude: 34.0522, longitude: -118.2437,
|
|
321
|
+
});
|
|
322
|
+
console.log(withoutCityInProvince.address); // 3568 S 940 E #20-58, CA US ZAP
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
For each signed projected axis:
|
|
326
|
+
|
|
327
|
+
1. Choose direction from its sign: negative northing is S, negative easting is W.
|
|
328
|
+
2. Take `wholeMeters = Math.floor(Math.abs(offset))`.
|
|
329
|
+
3. Calculate `block = Math.floor(wholeMeters / 100)`.
|
|
330
|
+
4. Calculate `remainder = wholeMeters % 100`.
|
|
331
|
+
5. Pad both remainders to two digits and join north/south first: `#03-06`.
|
|
332
|
+
|
|
333
|
+
Northing `-119416.594` and easting `38220.747` produce
|
|
334
|
+
`1194 S 382 E #16-20`. Magnitude `99.999` remains block 0, remainder 99;
|
|
335
|
+
magnitude 100 becomes block 1, remainder 00. Exact zero and negative zero use
|
|
336
|
+
N/E, but `-0.3` meters keeps S/W even though its whole-meter magnitude is zero.
|
|
337
|
+
Blocks may have any number of digits. Neither nearest-meter rounding nor old
|
|
338
|
+
5-meter snapping is applied.
|
|
339
|
+
|
|
340
|
+
The spherical azimuthal equidistant projection and Earth radius 6,371,008.8 meters
|
|
341
|
+
are unchanged. The grid axes remain fixed. A diagonal or curved street can change
|
|
342
|
+
both blocks; each address is independently derived from its GPS point. Roads
|
|
343
|
+
do not rotate the grid or introduce sequential house numbers. Moving 100
|
|
344
|
+
projected meters away from the origin along one axis increments that block while
|
|
345
|
+
preserving the other axis and both remainders.
|
|
346
|
+
|
|
347
|
+
## Country and province selectors
|
|
348
|
+
|
|
349
|
+
```js
|
|
350
|
+
import { getCountries, getCountry, getProvinces } from "@zukall/zap";
|
|
351
|
+
|
|
352
|
+
console.log(getCountries().length); // 239
|
|
353
|
+
console.log(getCountries().find(item => item.code === "US"));
|
|
354
|
+
// { code: "US", name: "United States", requiresProvince: true }
|
|
355
|
+
console.log(getProvinces("US").length); // 51
|
|
356
|
+
console.log(getProvinces("US").find(item => item.code === "FL"));
|
|
357
|
+
// { code: "FL", name: "Florida" }
|
|
358
|
+
console.log(getProvinces("UG")); // []
|
|
359
|
+
console.log(getCountry("UG").name); // Uganda
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Options are sorted by name, and each call returns a fresh array. Country/province
|
|
363
|
+
codes are trimmed and uppercased. Keep province codes as strings, preserving
|
|
364
|
+
leading zeros and source-specific codes returned by `getProvinces`.
|
|
365
|
+
|
|
366
|
+
These 22 countries require a configured province:
|
|
367
|
+
|
|
368
|
+
```text
|
|
369
|
+
DZ CD SD LY TD NE AO ML ZA ET MR RU CA US CN BR AU IN AR KZ SA ID
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
They contain 530 province grids. The remaining 217 country/territory entries use
|
|
373
|
+
country-wide grids. Select the country/province explicitly; the generator does
|
|
374
|
+
not infer membership from intersecting rectangles.
|
|
375
|
+
|
|
376
|
+
## Save the full record and keep unit details separate
|
|
377
|
+
|
|
378
|
+
```js
|
|
379
|
+
import { generateAddress } from "@zukall/zap";
|
|
380
|
+
|
|
381
|
+
const result = generateAddress({
|
|
382
|
+
countryCode: "UG", city: "Kampala", latitude: 0.3476, longitude: 32.5825,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const record = {
|
|
386
|
+
...result, // Address, blocks, original GPS, versions, center, encoding, gridId
|
|
387
|
+
street: "Example Road",
|
|
388
|
+
apartment: "12B",
|
|
389
|
+
floor: "3",
|
|
390
|
+
unit: "Office 4",
|
|
391
|
+
};
|
|
392
|
+
const saved = JSON.parse(JSON.stringify(record));
|
|
393
|
+
console.log(saved.address); // 1139 S 339 E #33-19, Kampala, UG ZAP
|
|
394
|
+
console.log(saved.unit); // Office 4 (separate from #33-19)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Persist the original address text and complete result, especially `coordinates`,
|
|
398
|
+
`protocolVersion`, `countryVersion`, `gridVersion`, and `gridId`. Treat `gridId` as
|
|
399
|
+
opaque. Cell identity uses that ID with the structured blocks, directions and
|
|
400
|
+
remainders; city/street/unit labels are independent display metadata. The package
|
|
401
|
+
does not include a reverse decoder or change your stored records.
|
|
402
|
+
|
|
403
|
+
## Open directions from saved GPS
|
|
404
|
+
|
|
405
|
+
```js
|
|
406
|
+
import { getDirectionsUrl } from "@zukall/zap";
|
|
407
|
+
|
|
408
|
+
// Use the stored record, including when it contains a protocol-1 address.
|
|
409
|
+
const url = getDirectionsUrl(saved);
|
|
410
|
+
console.log(url);
|
|
411
|
+
// https://www.google.com/maps/dir/?api=1&destination=0.3476%2C32.5825
|
|
412
|
+
|
|
413
|
+
// In a browser, open this from the user's Directions button:
|
|
414
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
The optional helper builds a [Google Maps directions URL](https://developers.google.com/maps/documentation/urls/get-started#directions)
|
|
418
|
+
from the original coordinates. It performs no API request and does not need an
|
|
419
|
+
API key. You can also pass `saved.coordinates` to your own map provider. Never
|
|
420
|
+
use the formatted ZAP string, grid center, city name, or coordinate suffix as the
|
|
421
|
+
routing destination.
|
|
422
|
+
|
|
423
|
+
## Upgrade from protocol 1 without changing issued addresses
|
|
424
|
+
|
|
425
|
+
Package `0.2.0` introduces protocol 2. `generateAddress` no longer accepts
|
|
426
|
+
`gridSizeMeters`; remove that option when intentionally issuing a new protocol-2
|
|
427
|
+
address. Passing it is rejected in TypeScript and at runtime, including when a
|
|
428
|
+
JavaScript caller passes `undefined`.
|
|
429
|
+
|
|
430
|
+
Previously issued records retain their original address, GPS point,
|
|
431
|
+
`protocolVersion: "1"`, grid interval and original `gridId`. Continue displaying
|
|
432
|
+
those saved values. Do not regenerate or overwrite them merely on package upgrade.
|
|
433
|
+
|
|
434
|
+
To explicitly reproduce a version-1 address, use its original GPS and interval:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
import {
|
|
438
|
+
generateLegacyAddress,
|
|
439
|
+
type LegacyAddressInput,
|
|
440
|
+
type LegacyAddressResult,
|
|
441
|
+
} from "@zukall/zap";
|
|
442
|
+
|
|
443
|
+
const legacyInput: LegacyAddressInput = {
|
|
444
|
+
countryCode: "UG",
|
|
445
|
+
latitude: 0.3476,
|
|
446
|
+
longitude: 32.5825,
|
|
447
|
+
gridSizeMeters: 5,
|
|
448
|
+
};
|
|
449
|
+
const original: LegacyAddressResult = generateLegacyAddress(legacyInput);
|
|
450
|
+
console.log(original.address); // 113935 S 33920 E, UG, ZAP
|
|
451
|
+
console.log(original.protocolVersion); // 1
|
|
452
|
+
console.log(original.gridSizeMeters); // 5
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
`generateLegacyAddress` retains the original result schema, nearest-interval
|
|
456
|
+
snapping and grid identity. Its default is 5 meters; supported original intervals
|
|
457
|
+
are integers 1–1000. `LEGACY_ZAP_PROTOCOL` exposes the frozen version-1 rules.
|
|
458
|
+
See [PROTOCOL-V1.md](PROTOCOL-V1.md).
|
|
459
|
+
|
|
460
|
+
If a user requests a new protocol-2 address for a saved location, explicitly
|
|
461
|
+
select its original country/province and GPS coordinates, omit the legacy grid
|
|
462
|
+
option, generate a **new record**, and retain the old record as history. Changing
|
|
463
|
+
the text of an old record without changing its protocol metadata is incorrect.
|
|
464
|
+
|
|
465
|
+
## Handle invalid input
|
|
466
|
+
|
|
467
|
+
```js
|
|
468
|
+
function tryAddress(input) {
|
|
469
|
+
try {
|
|
470
|
+
return { ok: true, result: generateAddress(input) };
|
|
471
|
+
} catch (error) {
|
|
472
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
console.log(tryAddress({ countryCode: "US", latitude: 34, longitude: -118 }));
|
|
477
|
+
// { ok: false, error: "Please select your province or state." }
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Validation rejects missing/non-string codes, unknown countries/provinces,
|
|
481
|
+
non-numeric or non-finite coordinates, latitude outside ±90, longitude outside
|
|
482
|
+
±180, coordinates outside the selected inclusive rectangle, non-string cities,
|
|
483
|
+
non-string street labels, and the obsolete `gridSizeMeters` option. Numeric strings must be converted before
|
|
484
|
+
calling. Empty city labels are omitted. Zero coordinates, date-line rectangles,
|
|
485
|
+
and Antarctica's full 360-degree rectangle remain supported.
|
|
486
|
+
|
|
487
|
+
## Inspect immutable source data
|
|
488
|
+
|
|
489
|
+
```js
|
|
490
|
+
import {
|
|
491
|
+
countries, provinceCountries, provinceGrids,
|
|
492
|
+
provinceCounts, provinceDataNotes, additionalSourceAreas,
|
|
493
|
+
} from "@zukall/zap/data";
|
|
494
|
+
|
|
495
|
+
console.log(countries.UG.corners); // Original four country corners
|
|
496
|
+
console.log(provinceCounts.US); // 51
|
|
497
|
+
console.log(provinceCounts.DZ); // 48 in this frozen snapshot
|
|
498
|
+
console.log(provinceDataNotes.DZ); // Historical coverage notes
|
|
499
|
+
console.log(provinceCountries.US.province.CA.name); // California
|
|
500
|
+
console.log(provinceGrids.US.provinces.CA.name); // California
|
|
501
|
+
console.log(Object.keys(additionalSourceAreas).length); // 13
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
The data is the unchanged Natural Earth snapshot, with historical divisions,
|
|
505
|
+
source-specific keys, and some territorial groupings. It supplies fixed grid
|
|
506
|
+
origins rather than a current postal/administrative directory. The 13 additional
|
|
507
|
+
source areas remain outside address generation. Bounds and versions are frozen at
|
|
508
|
+
runtime; future changes require new snapshots and preservation of older versions.
|
|
509
|
+
See [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) and [PROTOCOL.md](PROTOCOL.md).
|
|
510
|
+
|
|
511
|
+
## Public API
|
|
512
|
+
|
|
513
|
+
| Export | Purpose |
|
|
514
|
+
| --- | --- |
|
|
515
|
+
| `generateAddress(input)` | Generate a protocol-2 address with fixed 100-meter blocks and 1-meter detail. |
|
|
516
|
+
| `generateLegacyAddress(input)` | Explicitly reproduce the original protocol-1 address and grid ID. |
|
|
517
|
+
| `getDirectionsUrl(savedRecord)` | Build directions using original saved GPS coordinates, for either version. |
|
|
518
|
+
| `getCountries()` / `getCountry(code)` | Select or inspect immutable country configurations. |
|
|
519
|
+
| `getProvinces(code)` | List configured provinces; country-wide grids return `[]`. |
|
|
520
|
+
| `ZAP_PROTOCOL` / `LEGACY_ZAP_PROTOCOL` | Read current or original protocol constants. |
|
|
521
|
+
| `zap` | Access the same functions through a named namespace. |
|
|
522
|
+
| `@zukall/zap/data` | Inspect frozen snapshots, counts, notes and additional source areas. |
|
|
523
|
+
|
|
524
|
+
## Tests, packaging, and publication
|
|
525
|
+
|
|
526
|
+
```sh
|
|
527
|
+
npm run typecheck
|
|
528
|
+
npm test
|
|
529
|
+
npm run pack:check
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Tests cover the requested formats, all directions, zero and sub-meter negatives,
|
|
533
|
+
99/100-meter boundaries, two-digit suffixes, country/province selection, unchanged
|
|
534
|
+
grid identity components, date-line rectangles, diagonal sequences, 100-meter
|
|
535
|
+
axis movements, old-address compatibility, saved GPS directions, and all centers
|
|
536
|
+
and corners of 747 configured grids. Package verification checks ESM/CommonJS
|
|
537
|
+
runtime exports and compiles both TypeScript consumer formats, including the
|
|
538
|
+
removed-option error.
|
|
539
|
+
|
|
540
|
+
`npm pack` also runs the checks through `prepack`. Publishing is a separate action:
|
|
541
|
+
|
|
542
|
+
```sh
|
|
543
|
+
npm login
|
|
544
|
+
npm run pack:check
|
|
545
|
+
npm publish ./.artifacts/zukall-zap-0.2.1.tgz --access public
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
An npm account with permission to publish `@zukall/zap` is required. Building and
|
|
549
|
+
packing do not publish the package. Package version is `0.2.1`; current protocol
|
|
550
|
+
version is `2`, while preserved legacy results remain version `1`.
|
|
551
|
+
|
|
552
|
+
## License
|
|
553
|
+
|
|
554
|
+
Code: [MIT](LICENSE). Natural Earth data: public domain, documented in
|
|
555
|
+
[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Bundled map data
|
|
2
|
+
|
|
3
|
+
The country and province grid rectangles derive from Natural Earth at 1:10 million
|
|
4
|
+
scale, using WGS84 coordinates. Natural Earth places its vector and raster data in
|
|
5
|
+
the public domain: https://www.naturalearthdata.com/about/terms-of-use/.
|
|
6
|
+
|
|
7
|
+
- Countries: https://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-admin-0-countries/
|
|
8
|
+
- Provinces: https://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-admin-1-states-provinces/
|
|
9
|
+
- Province snapshot SHA256 recorded by the source files:
|
|
10
|
+
`22d0e3ad85eb3e27f17cabf8ba2d50e554fbc27a87796ff891d958185da62fb5`.
|
|
11
|
+
- Retrieval date recorded by the supplied files: 2026-10-10.
|
|
12
|
+
|
|
13
|
+
The source files preserve their original bounds and snapshot versions. Province
|
|
14
|
+
counts and historical coverage notes are available from `@zukall/zap/data`.
|
|
15
|
+
The recorded retrieval date does not establish the current validity of administrative
|
|
16
|
+
divisions. Bounding rectangles can overlap and do not establish administrative membership.
|
|
17
|
+
|
|
18
|
+
The MIT license covers this package's code. Natural Earth's public-domain terms
|
|
19
|
+
apply to the underlying map data.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateAddress = generateAddress;
|
|
4
|
+
const encoding_js_1 = require("./encoding.js");
|
|
5
|
+
const grid_js_1 = require("./grid.js");
|
|
6
|
+
const protocol_js_1 = require("./protocol.js");
|
|
7
|
+
function displayLabel(value, name) {
|
|
8
|
+
if (value !== undefined && typeof value !== "string") {
|
|
9
|
+
throw new TypeError(`${name} must be a string when supplied.`);
|
|
10
|
+
}
|
|
11
|
+
return value?.trim().replace(/\s+/g, " ") || undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Generate a protocol-2 address directly from the original GPS coordinates. */
|
|
14
|
+
function generateAddress(input) {
|
|
15
|
+
if (input && typeof input === "object" && "gridSizeMeters" in input) {
|
|
16
|
+
throw new TypeError("gridSizeMeters is a protocol-1 option. Remove it for ZAP 2, or use generateLegacyAddress to reproduce an original address.");
|
|
17
|
+
}
|
|
18
|
+
const { identityComponents, ...grid } = (0, grid_js_1.projectAddress)(input);
|
|
19
|
+
const city = displayLabel(input.city, "city");
|
|
20
|
+
const street = displayLabel(input.street, "street");
|
|
21
|
+
const encoded = (0, encoding_js_1.encodeOffsets)(grid.northingMeters, grid.eastingMeters);
|
|
22
|
+
const location = [grid.provinceCode, grid.countryCode, protocol_js_1.ZAP_PROTOCOL.addressSuffix]
|
|
23
|
+
.filter(Boolean).join(" ");
|
|
24
|
+
const position = `${encoded.northSouthBlock} ${encoded.northSouthDirection} ${encoded.eastWestBlock} ${encoded.eastWestDirection} ${encoded.coordinateSuffix}`;
|
|
25
|
+
return {
|
|
26
|
+
protocol: protocol_js_1.ZAP_PROTOCOL.id,
|
|
27
|
+
protocolVersion: protocol_js_1.ZAP_PROTOCOL.version,
|
|
28
|
+
address: [position, ...(city ? [city] : []), location].join(", "),
|
|
29
|
+
...grid,
|
|
30
|
+
...(city ? { city } : {}),
|
|
31
|
+
...(street ? { street } : {}),
|
|
32
|
+
...encoded,
|
|
33
|
+
blockSizeMeters: protocol_js_1.ZAP_PROTOCOL.blockSizeMeters,
|
|
34
|
+
detailSizeMeters: protocol_js_1.ZAP_PROTOCOL.detailSizeMeters,
|
|
35
|
+
encoding: protocol_js_1.ZAP_PROTOCOL.encoding,
|
|
36
|
+
gridId: [protocol_js_1.ZAP_PROTOCOL.id, protocol_js_1.ZAP_PROTOCOL.version, ...identityComponents, protocol_js_1.ZAP_PROTOCOL.encoding].join(":"),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct country/province configuration for generateAddress.
|
|
3
|
+
* Natural Earth 1:10 million, WGS84 degrees, public-domain map data.
|
|
4
|
+
* Sources retrieved 2026-10-10:
|
|
5
|
+
* https://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-admin-0-countries/
|
|
6
|
+
* https://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-admin-1-states-provinces/
|
|
7
|
+
*
|
|
8
|
+
* 239 country/territory keys; 22 countries use province grids, including US states.
|
|
9
|
+
* The remaining 217 entries use country-wide rectangles. No invented province codes.
|
|
10
|
+
* Source units without ISO/EH codes are exported separately under NE_ keys.
|
|
11
|
+
* Some territories are grouped under a parent: this is not ISO-249 coverage.
|
|
12
|
+
* XK is the source's Kosovo code, not an officially assigned ISO code.
|
|
13
|
+
* Source de facto territorial grouping is retained.
|
|
14
|
+
*
|
|
15
|
+
* Corner order: topLeft, topRight, bottomRight, bottomLeft.
|
|
16
|
+
* Numbers are rounded outward to six decimals to contain source geometry.
|
|
17
|
+
* Six decimals are representation precision, not centimeter boundary accuracy.
|
|
18
|
+
* Rectangles can overlap; administrative membership must be verified separately.
|
|
19
|
+
* For date-line crossings west > east. ZAP supports these rectangles.
|
|
20
|
+
* AQ (Antarctica) spans all longitudes. ZAP handles its explicit 360-degree span.
|
|
21
|
+
* US territories such as PR and GU have separate country/territory entries.
|
|
22
|
+
* These versions and corners are frozen at runtime. Updates require a new snapshot.
|
|
23
|
+
*/
|
|
24
|
+
import type { Country } from "./types.js";
|
|
25
|
+
export declare const countries: Readonly<Record<string, Country>>;
|
|
26
|
+
export declare const additionalSourceAreas: Readonly<Record<string, Country>>;
|