@zukall/zap 0.2.3 → 0.3.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/PROTOCOL.md +1 -1
- package/README.md +201 -4
- package/asset/zukall-haiti-country-grid.webp +0 -0
- package/dist/cjs/distance.d.ts +8 -0
- package/dist/cjs/distance.js +61 -0
- package/dist/cjs/index.d.ts +11 -2
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/locations.d.ts +11 -0
- package/dist/cjs/locations.js +67 -0
- package/dist/cjs/route.d.ts +10 -0
- package/dist/cjs/route.js +289 -0
- package/dist/cjs/types.d.ts +70 -0
- package/dist/esm/distance.d.ts +8 -0
- package/dist/esm/distance.js +55 -0
- package/dist/esm/index.d.ts +11 -2
- package/dist/esm/index.js +6 -1
- package/dist/esm/locations.d.ts +11 -0
- package/dist/esm/locations.js +61 -0
- package/dist/esm/route.d.ts +10 -0
- package/dist/esm/route.js +285 -0
- package/dist/esm/types.d.ts +70 -0
- package/package.json +2 -2
package/PROTOCOL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ZAP — Zukall Addressing Protocol, version 2
|
|
2
2
|
|
|
3
3
|
Protocol 2 uses **100-meter grid blocks** and **1-meter detail**, computed from
|
|
4
|
-
unrounded projected GPS coordinates. The npm package version (`0.
|
|
4
|
+
unrounded projected GPS coordinates. The npm package version (`0.3.0`) and
|
|
5
5
|
protocol version (`2`) are separate. Previously issued protocol-1 addresses retain
|
|
6
6
|
their original text and identity; see [PROTOCOL-V1.md](PROTOCOL-V1.md).
|
|
7
7
|
|
package/README.md
CHANGED
|
@@ -223,7 +223,7 @@ npm install @zukall/zap
|
|
|
223
223
|
Before publication, install the verified local package:
|
|
224
224
|
|
|
225
225
|
```sh
|
|
226
|
-
npm install /absolute/path/to/zukall-address-protocol/.artifacts/zukall-zap-0.
|
|
226
|
+
npm install /absolute/path/to/zukall-address-protocol/.artifacts/zukall-zap-0.3.0.tgz
|
|
227
227
|
```
|
|
228
228
|
|
|
229
229
|
Build and verify the source:
|
|
@@ -481,6 +481,196 @@ API key. You can also pass `saved.coordinates` to your own map provider. Never
|
|
|
481
481
|
use the formatted ZAP string, grid center, city name, or coordinate suffix as the
|
|
482
482
|
routing destination.
|
|
483
483
|
|
|
484
|
+
## Measure distance between locations
|
|
485
|
+
|
|
486
|
+
**ZAP’s strongest advantage here is making distance understandable to people;
|
|
487
|
+
software can handle either easily.** Within one grid, a difference of three
|
|
488
|
+
blocks along an axis represents 300 projected meters. That is easier for a
|
|
489
|
+
person to read than comparing decimal GPS coordinates. Software can calculate
|
|
490
|
+
distance from either representation.
|
|
491
|
+
|
|
492
|
+
Use complete saved records whenever available. `getDistance` defaults to the
|
|
493
|
+
original GPS coordinates, including when the records come from different
|
|
494
|
+
countries, states, or protocol versions:
|
|
495
|
+
|
|
496
|
+
```js
|
|
497
|
+
import { generateAddress, getDistance } from "@zukall/zap";
|
|
498
|
+
|
|
499
|
+
const depot = generateAddress({
|
|
500
|
+
countryCode: "UG", city: "Kampala", latitude: 0.3476, longitude: 32.5825,
|
|
501
|
+
});
|
|
502
|
+
const pickup = generateAddress({
|
|
503
|
+
countryCode: "UG", city: "Kampala", latitude: 0.3486, longitude: 32.5825,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const distance = getDistance(depot, pickup);
|
|
507
|
+
console.log(distance.method); // haversine
|
|
508
|
+
console.log(distance.meters.toFixed(1)); // 111.2
|
|
509
|
+
console.log(distance.kilometers.toFixed(4)); // 0.1112
|
|
510
|
+
|
|
511
|
+
// Latitude/longitude objects also work.
|
|
512
|
+
getDistance(depot.coordinates, pickup.coordinates);
|
|
513
|
+
|
|
514
|
+
// Optional distance in the saved grid's projected plane.
|
|
515
|
+
const gridDistance = getDistance(depot, pickup, { method: "projected" });
|
|
516
|
+
console.log(gridDistance.method); // projected
|
|
517
|
+
console.log(gridDistance.gridId === depot.gridId); // true
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Haversine measures the geographic great-circle distance on a sphere, using the
|
|
521
|
+
same **6,371,008.8-meter Earth radius** as the address projection. Values retain
|
|
522
|
+
number precision; rounding above is only for display. It does not measure road
|
|
523
|
+
length, altitude, or terrain.
|
|
524
|
+
|
|
525
|
+
Projected mode uses `Math.hypot()` on the stored northing/easting differences.
|
|
526
|
+
Both records must have **exactly the same `gridId`** and finite offsets. A
|
|
527
|
+
different country, province, snapshot, or encoding ID is rejected. This mode
|
|
528
|
+
does not regenerate addresses or reproject older records; protocol-1 offsets
|
|
529
|
+
can retain their original snapping. Projected-plane distances can differ from
|
|
530
|
+
geographic distances away from the grid origin. Neither mode parses the address
|
|
531
|
+
text, subtracts unit numbers, or treats the suffix as a house number.
|
|
532
|
+
|
|
533
|
+
## Optimize randomly entered stops
|
|
534
|
+
|
|
535
|
+
`optimizeRoute` returns an **optimized straight-line itinerary**. It chooses an
|
|
536
|
+
order that reduces the sum of distances between consecutive stops. Your
|
|
537
|
+
application can use that order to plan visits, then open map directions from
|
|
538
|
+
each stop's saved GPS point. It does not provide driving directions or guarantee
|
|
539
|
+
the shortest journey along roads.
|
|
540
|
+
|
|
541
|
+
This example uses illustrative GPS points along a meridian, entered out of order:
|
|
542
|
+
|
|
543
|
+
```js
|
|
544
|
+
import { generateAddress, optimizeRoute } from "@zukall/zap";
|
|
545
|
+
|
|
546
|
+
const stop = (id, latitude) => ({
|
|
547
|
+
id,
|
|
548
|
+
...generateAddress({
|
|
549
|
+
countryCode: "UG", city: "Kampala", latitude, longitude: 32.5825,
|
|
550
|
+
}),
|
|
551
|
+
});
|
|
552
|
+
const stops = [
|
|
553
|
+
stop("depot", 0.3476),
|
|
554
|
+
stop("C", 0.3506),
|
|
555
|
+
stop("A", 0.3486),
|
|
556
|
+
stop("B", 0.3496),
|
|
557
|
+
];
|
|
558
|
+
|
|
559
|
+
const itinerary = optimizeRoute(stops);
|
|
560
|
+
console.log(itinerary.description); // optimized straight-line itinerary
|
|
561
|
+
console.log(itinerary.orderedStops.map(stop => stop.id)); // ["depot", "A", "B", "C"]
|
|
562
|
+
console.log(itinerary.originalIndices); // [0, 2, 3, 1]
|
|
563
|
+
console.log(itinerary.totalMeters.toFixed(1)); // 333.6
|
|
564
|
+
console.log(itinerary.originalTotalMeters.toFixed(1)); // 667.2
|
|
565
|
+
console.log(itinerary.optimization); // exact
|
|
566
|
+
console.log(itinerary.algorithm); // held-karp
|
|
567
|
+
console.log(itinerary.method); // haversine
|
|
568
|
+
|
|
569
|
+
for (const leg of itinerary.legs) {
|
|
570
|
+
console.log(stops[leg.fromIndex].id, stops[leg.toIndex].id, leg.meters);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Select endpoints by their ORIGINAL input indices.
|
|
574
|
+
const fixedEndpoints = optimizeRoute(stops, { startIndex: 2, endIndex: 1 });
|
|
575
|
+
// Starts at A (input 2), finishes at C (input 1), and includes every other stop.
|
|
576
|
+
|
|
577
|
+
const roundTrip = optimizeRoute(stops, { returnToStart: true });
|
|
578
|
+
console.log(roundTrip.legs.at(-1).isReturn); // true
|
|
579
|
+
// orderedStops still includes the depot only once; legs include the return to it.
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
| Option | Behavior |
|
|
583
|
+
| --- | --- |
|
|
584
|
+
| `startIndex` | Fixed start selected from the supplied stops; defaults to input index `0`. |
|
|
585
|
+
| `endIndex` | Optional fixed final stop, selected by original index. |
|
|
586
|
+
| `returnToStart` | Adds a closing leg to the start; defaults to `false` and cannot be combined with `endIndex`. |
|
|
587
|
+
| `method` | `"haversine"` by default, or `"projected"` when every saved record has the identical grid ID. |
|
|
588
|
+
| `resolveAddress` | Caller-owned lookup for address strings; supplying it makes the call return a Promise. |
|
|
589
|
+
| `distanceMatrix` | Optional meter costs indexed by original stop indices; cannot be combined with `method`. |
|
|
590
|
+
|
|
591
|
+
Endpoints must be supplied stops. If a depot is outside your delivery list,
|
|
592
|
+
include it in `points` and select its index. An open itinerary with multiple
|
|
593
|
+
stops cannot have the same start and end; choose `returnToStart` for that case.
|
|
594
|
+
|
|
595
|
+
The result contains the original objects or strings in `orderedStops`, their
|
|
596
|
+
`originalIndices`, `resolvedPoints` in the same order, and `legs` with
|
|
597
|
+
`fromIndex`, `toIndex`, meters, kilometers, method, and `isReturn`. Totals are
|
|
598
|
+
available as `totalMeters` and `totalKilometers`. Inputs and saved addresses are
|
|
599
|
+
not modified. Duplicate coordinates and duplicate references remain separate
|
|
600
|
+
stops and retain their own indices.
|
|
601
|
+
|
|
602
|
+
### Exact and approximate optimization
|
|
603
|
+
|
|
604
|
+
- **Up to 12 stops:** Held–Karp subset dynamic programming finds an exact minimum
|
|
605
|
+
for the selected distance costs and endpoint constraints.
|
|
606
|
+
- **13–1000 stops:** Deterministic nearest neighbor followed by 2-opt returns an
|
|
607
|
+
approximate order. Both the nearest-neighbor order and the original feasible
|
|
608
|
+
order are improved, with at most 100 2-opt passes per seed. Fixed endpoints
|
|
609
|
+
remain fixed. Results are never longer than the original feasible order.
|
|
610
|
+
|
|
611
|
+
`optimization` reports `"exact"` or `"approximate"`; `algorithm` names the
|
|
612
|
+
algorithm, and `exactCutoff` is `12`. Equal-cost choices use original input
|
|
613
|
+
indices deterministically. `ROUTE_OPTIMIZATION` exports the frozen work limits.
|
|
614
|
+
The original feasible order moves the selected start first and end last, keeping
|
|
615
|
+
the other stops in their entered order; its cost is `originalTotalMeters`.
|
|
616
|
+
|
|
617
|
+
Empty lists and single stops return an exact zero-distance itinerary with no
|
|
618
|
+
legs, even for a round trip. Explicit endpoint indices must still be valid.
|
|
619
|
+
Invalid coordinates, sparse arrays, contradictory endpoints, and lists above
|
|
620
|
+
1000 stops are rejected. Optimizing many stops uses a quadratic distance matrix;
|
|
621
|
+
approximate results are not a guarantee of the best possible order.
|
|
622
|
+
|
|
623
|
+
### Address strings need saved-record lookups
|
|
624
|
+
|
|
625
|
+
The package has **no reverse decoder or automatic geocoder**. A text-only ZAP
|
|
626
|
+
address cannot restore the original GPS point. Supply a resolver that retrieves
|
|
627
|
+
the corresponding saved record; it may return the record directly or use an
|
|
628
|
+
asynchronous database lookup:
|
|
629
|
+
|
|
630
|
+
```js
|
|
631
|
+
const savedByAddress = new Map(stops.map(record => [record.address, record]));
|
|
632
|
+
const resolveAddress = async (address, originalIndex) => {
|
|
633
|
+
const record = savedByAddress.get(address);
|
|
634
|
+
if (!record) throw new Error(`No saved GPS record for input ${originalIndex}`);
|
|
635
|
+
return record;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const textStops = stops.map(record => record.address);
|
|
639
|
+
const itineraryFromText = await optimizeRoute(textStops, { resolveAddress });
|
|
640
|
+
const distanceFromText = await getDistance(textStops[0], textStops[1], { resolveAddress });
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Resolvers receive each string and its original input index (0/1 for
|
|
644
|
+
`getDistance`), once per string occurrence. The original strings remain in
|
|
645
|
+
`orderedStops`; retrieved records are in `resolvedPoints`. A resolver must return
|
|
646
|
+
a saved record with valid `coordinates`; missing records and lookup failures
|
|
647
|
+
reject the Promise. Direct GPS/record calls without a resolver stay synchronous.
|
|
648
|
+
Use full records and your own stop IDs when addresses are shared by different
|
|
649
|
+
units or records; an address string alone does not identify a unique household.
|
|
650
|
+
|
|
651
|
+
### Supplying distance costs from another provider
|
|
652
|
+
|
|
653
|
+
The optimizer consumes a distance matrix internally. Your application can supply
|
|
654
|
+
one from a future road-distance provider without changing stop identities:
|
|
655
|
+
|
|
656
|
+
```js
|
|
657
|
+
const matrix = [
|
|
658
|
+
[0, 400, 100],
|
|
659
|
+
[350, 0, 200],
|
|
660
|
+
[120, 220, 0],
|
|
661
|
+
];
|
|
662
|
+
const suppliedCosts = optimizeRoute(stops.slice(0, 3), { distanceMatrix: matrix });
|
|
663
|
+
console.log(suppliedCosts.method); // distance-matrix
|
|
664
|
+
console.log(suppliedCosts.description); // optimized distance-matrix itinerary
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
Rows/columns follow **original input order**, entries are nonnegative finite
|
|
668
|
+
**meters**, and the diagonal must be zero. Matrices may be asymmetric; both
|
|
669
|
+
algorithms account for the full cost of reversing directed legs. Values must
|
|
670
|
+
leave room for finite route totals. The package does not fetch provider data.
|
|
671
|
+
Results are labeled separately when caller-supplied costs are used. The function
|
|
672
|
+
still returns a stop order, not driving instructions or a vehicle scheduling plan.
|
|
673
|
+
|
|
484
674
|
## Upgrade from protocol 1 without changing issued addresses
|
|
485
675
|
|
|
486
676
|
Package `0.2.0` introduces protocol 2. `generateAddress` no longer accepts
|
|
@@ -576,6 +766,9 @@ See [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) and [PROTOCOL.md](PROTOCOL.
|
|
|
576
766
|
| `generateAddress(input)` | Generate a protocol-2 address with fixed 100-meter blocks and 1-meter detail. |
|
|
577
767
|
| `generateLegacyAddress(input)` | Explicitly reproduce the original protocol-1 address and grid ID. |
|
|
578
768
|
| `getDirectionsUrl(savedRecord)` | Build directions using original saved GPS coordinates, for either version. |
|
|
769
|
+
| `getDistance(pointA, pointB, options?)` | Haversine meters/kilometers from GPS, or projected distance within one exact grid ID. |
|
|
770
|
+
| `optimizeRoute(points, options?)` | Exact or approximate stop order, original indices, legs and totals for an optimized straight-line itinerary. |
|
|
771
|
+
| `ROUTE_OPTIMIZATION` | Inspect the 12-stop exact cutoff, 1000-stop limit and 100-pass heuristic work bound. |
|
|
579
772
|
| `getCountries()` / `getCountry(code)` | Select or inspect immutable country configurations. |
|
|
580
773
|
| `getProvinces(code)` | List configured provinces; country-wide grids return `[]`. |
|
|
581
774
|
| `ZAP_PROTOCOL` / `LEGACY_ZAP_PROTOCOL` | Read current or original protocol constants. |
|
|
@@ -594,7 +787,11 @@ Tests cover the requested formats, all directions, zero and sub-meter negatives,
|
|
|
594
787
|
99/100-meter boundaries, two-digit suffixes, country/province selection, unchanged
|
|
595
788
|
grid identity components, date-line rectangles, diagonal sequences, 100-meter
|
|
596
789
|
axis movements, old-address compatibility, saved GPS directions, and all centers
|
|
597
|
-
and corners of 747 configured grids.
|
|
790
|
+
and corners of 747 configured grids. Distance/itinerary tests check known
|
|
791
|
+
distances, mixed grids, endpoints, return legs, duplicate identities, explicit
|
|
792
|
+
resolvers, and exact solutions against an independent exhaustive search.
|
|
793
|
+
Heuristics are checked against the original feasible order, including directed
|
|
794
|
+
matrices. Package verification checks ESM/CommonJS
|
|
598
795
|
runtime exports and compiles both TypeScript consumer formats, including the
|
|
599
796
|
removed-option error.
|
|
600
797
|
|
|
@@ -603,11 +800,11 @@ removed-option error.
|
|
|
603
800
|
```sh
|
|
604
801
|
npm login
|
|
605
802
|
npm run pack:check
|
|
606
|
-
npm publish ./.artifacts/zukall-zap-0.
|
|
803
|
+
npm publish ./.artifacts/zukall-zap-0.3.0.tgz --access public
|
|
607
804
|
```
|
|
608
805
|
|
|
609
806
|
An npm account with permission to publish `@zukall/zap` is required. Building and
|
|
610
|
-
packing do not publish the package. Package version is `0.
|
|
807
|
+
packing do not publish the package. Package version is `0.3.0`; current protocol
|
|
611
808
|
version is `2`, while preserved legacy results remain version `1`.
|
|
612
809
|
|
|
613
810
|
## License
|
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Coordinates, DistanceMethod, DistanceOptions, DistanceResult, LocationInput, LocationPoint, ResolvedDistanceOptions } from "./types.js";
|
|
2
|
+
export declare function getDistanceMethod(method: DistanceMethod | undefined): DistanceMethod;
|
|
3
|
+
/** Geographic great-circle surface distance, using the existing spherical Earth radius. */
|
|
4
|
+
export declare function haversineMeters(pointA: Coordinates, pointB: Coordinates): number;
|
|
5
|
+
export declare function requireFiniteDistance(meters: number): number;
|
|
6
|
+
export declare function getDistance(pointA: LocationPoint, pointB: LocationPoint, options?: DistanceOptions): DistanceResult;
|
|
7
|
+
export declare function getDistance(pointA: LocationInput, pointB: LocationInput, options: ResolvedDistanceOptions): Promise<DistanceResult>;
|
|
8
|
+
export declare function getDistance(pointA: LocationPoint, pointB: LocationPoint, options: DistanceOptions | ResolvedDistanceOptions): DistanceResult | Promise<DistanceResult>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDistanceMethod = getDistanceMethod;
|
|
4
|
+
exports.haversineMeters = haversineMeters;
|
|
5
|
+
exports.requireFiniteDistance = requireFiniteDistance;
|
|
6
|
+
exports.getDistance = getDistance;
|
|
7
|
+
const protocol_js_1 = require("./protocol.js");
|
|
8
|
+
const locations_js_1 = require("./locations.js");
|
|
9
|
+
function getDistanceMethod(method) {
|
|
10
|
+
if (method !== undefined && method !== "haversine" && method !== "projected") {
|
|
11
|
+
throw new TypeError("method must be haversine or projected.");
|
|
12
|
+
}
|
|
13
|
+
return method ?? "haversine";
|
|
14
|
+
}
|
|
15
|
+
/** Geographic great-circle surface distance, using the existing spherical Earth radius. */
|
|
16
|
+
function haversineMeters(pointA, pointB) {
|
|
17
|
+
const radians = (degrees) => degrees * Math.PI / 180;
|
|
18
|
+
const latitudeA = radians(pointA.latitude);
|
|
19
|
+
const latitudeB = radians(pointB.latitude);
|
|
20
|
+
let longitudeDifference = pointB.longitude - pointA.longitude;
|
|
21
|
+
if (longitudeDifference > 180)
|
|
22
|
+
longitudeDifference -= 360;
|
|
23
|
+
if (longitudeDifference < -180)
|
|
24
|
+
longitudeDifference += 360;
|
|
25
|
+
const cosineA = Math.abs(pointA.latitude) === 90 ? 0 : Math.cos(latitudeA);
|
|
26
|
+
const cosineB = Math.abs(pointB.latitude) === 90 ? 0 : Math.cos(latitudeB);
|
|
27
|
+
const haversine = Math.sin(radians(pointB.latitude - pointA.latitude) / 2) ** 2
|
|
28
|
+
+ cosineA * cosineB * Math.sin(radians(longitudeDifference) / 2) ** 2;
|
|
29
|
+
const clamped = Math.max(0, Math.min(1, haversine));
|
|
30
|
+
return 2 * protocol_js_1.ZAP_PROTOCOL.earthRadiusMeters * Math.atan2(Math.sqrt(clamped), Math.sqrt(1 - clamped));
|
|
31
|
+
}
|
|
32
|
+
function requireFiniteDistance(meters) {
|
|
33
|
+
if (!Number.isFinite(meters) || meters < 0)
|
|
34
|
+
throw new RangeError("Distance must be finite and nonnegative.");
|
|
35
|
+
return meters === 0 ? 0 : meters;
|
|
36
|
+
}
|
|
37
|
+
function calculateDistance(points, method) {
|
|
38
|
+
const pointA = points[0];
|
|
39
|
+
const pointB = points[1];
|
|
40
|
+
const coordinatesA = (0, locations_js_1.getLocationCoordinates)(pointA);
|
|
41
|
+
const coordinatesB = (0, locations_js_1.getLocationCoordinates)(pointB);
|
|
42
|
+
if (method === "projected") {
|
|
43
|
+
const gridA = (0, locations_js_1.getProjectedPoint)(pointA);
|
|
44
|
+
const gridB = (0, locations_js_1.getProjectedPoint)(pointB);
|
|
45
|
+
if (gridA.gridId !== gridB.gridId) {
|
|
46
|
+
throw new RangeError("Projected distance requires exactly the same gridId.");
|
|
47
|
+
}
|
|
48
|
+
const meters = requireFiniteDistance(Math.hypot(gridB.northingMeters - gridA.northingMeters, gridB.eastingMeters - gridA.eastingMeters));
|
|
49
|
+
return { method, meters, kilometers: meters / 1000, gridId: gridA.gridId };
|
|
50
|
+
}
|
|
51
|
+
const meters = requireFiniteDistance(haversineMeters(coordinatesA, coordinatesB));
|
|
52
|
+
return { method, meters, kilometers: meters / 1000 };
|
|
53
|
+
}
|
|
54
|
+
/** GPS by default; supplying a resolver returns a Promise and never silently geocodes text. */
|
|
55
|
+
function getDistance(pointA, pointB, options = {}) {
|
|
56
|
+
(0, locations_js_1.validateLocationOptions)(options);
|
|
57
|
+
const method = getDistanceMethod(options.method);
|
|
58
|
+
const resolved = (0, locations_js_1.resolveLocationPoints)([pointA, pointB], options.resolveAddress);
|
|
59
|
+
return resolved instanceof Promise
|
|
60
|
+
? resolved.then(points => calculateDistance(points, method)) : calculateDistance(resolved, method);
|
|
61
|
+
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { generateAddress } from "./address.js";
|
|
2
2
|
import { generateLegacyAddress } from "./legacy-address.js";
|
|
3
3
|
import { getDirectionsUrl } from "./directions.js";
|
|
4
|
+
import { getDistance } from "./distance.js";
|
|
5
|
+
import { optimizeRoute, ROUTE_OPTIMIZATION } from "./route.js";
|
|
4
6
|
import { getCountries, getCountry, getProvinces } from "./country.js";
|
|
5
7
|
import { ZAP_PROTOCOL, LEGACY_ZAP_PROTOCOL } from "./protocol.js";
|
|
6
|
-
export { generateAddress, generateLegacyAddress, getDirectionsUrl, getCountries, getCountry, getProvinces, ZAP_PROTOCOL, LEGACY_ZAP_PROTOCOL, };
|
|
7
|
-
export type { AddressInput, AddressResult, AddressEncoding, LegacyAddressInput, LegacyAddressResult, Coordinates, Corners, GridInput, Country, CountryOption, Province, ProvinceCountry, ProvinceOption, } from "./types.js";
|
|
8
|
+
export { generateAddress, generateLegacyAddress, getDirectionsUrl, getDistance, optimizeRoute, ROUTE_OPTIMIZATION, getCountries, getCountry, getProvinces, ZAP_PROTOCOL, LEGACY_ZAP_PROTOCOL, };
|
|
9
|
+
export type { AddressInput, AddressResult, AddressEncoding, LegacyAddressInput, LegacyAddressResult, Coordinates, Corners, GridInput, Country, CountryOption, Province, ProvinceCountry, ProvinceOption, SavedLocationPoint, LocationPoint, LocationInput, AddressResolver, DistanceMethod, RouteDistanceMethod, DistanceOptions, ResolvedDistanceOptions, DistanceResult, DistanceMatrix, RouteOptions, ResolvedRouteOptions, RouteLeg, RouteResult, } from "./types.js";
|
|
8
10
|
/** Named namespace for applications that refer to the protocol as `zap`. */
|
|
9
11
|
export declare const zap: Readonly<{
|
|
10
12
|
protocol: Readonly<{
|
|
@@ -21,6 +23,13 @@ export declare const zap: Readonly<{
|
|
|
21
23
|
generateAddress: typeof generateAddress;
|
|
22
24
|
generateLegacyAddress: typeof generateLegacyAddress;
|
|
23
25
|
getDirectionsUrl: typeof getDirectionsUrl;
|
|
26
|
+
getDistance: typeof getDistance;
|
|
27
|
+
optimizeRoute: typeof optimizeRoute;
|
|
28
|
+
routeOptimization: Readonly<{
|
|
29
|
+
readonly exactMaxStops: 12;
|
|
30
|
+
readonly maxStops: 1000;
|
|
31
|
+
readonly twoOptMaxPasses: 100;
|
|
32
|
+
}>;
|
|
24
33
|
getCountries: typeof getCountries;
|
|
25
34
|
getCountry: typeof getCountry;
|
|
26
35
|
getProvinces: typeof getProvinces;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.zap = exports.LEGACY_ZAP_PROTOCOL = exports.ZAP_PROTOCOL = exports.getProvinces = exports.getCountry = exports.getCountries = exports.getDirectionsUrl = exports.generateLegacyAddress = exports.generateAddress = void 0;
|
|
3
|
+
exports.zap = exports.LEGACY_ZAP_PROTOCOL = exports.ZAP_PROTOCOL = exports.getProvinces = exports.getCountry = exports.getCountries = exports.ROUTE_OPTIMIZATION = exports.optimizeRoute = exports.getDistance = exports.getDirectionsUrl = exports.generateLegacyAddress = exports.generateAddress = void 0;
|
|
4
4
|
const address_js_1 = require("./address.js");
|
|
5
5
|
Object.defineProperty(exports, "generateAddress", { enumerable: true, get: function () { return address_js_1.generateAddress; } });
|
|
6
6
|
const legacy_address_js_1 = require("./legacy-address.js");
|
|
7
7
|
Object.defineProperty(exports, "generateLegacyAddress", { enumerable: true, get: function () { return legacy_address_js_1.generateLegacyAddress; } });
|
|
8
8
|
const directions_js_1 = require("./directions.js");
|
|
9
9
|
Object.defineProperty(exports, "getDirectionsUrl", { enumerable: true, get: function () { return directions_js_1.getDirectionsUrl; } });
|
|
10
|
+
const distance_js_1 = require("./distance.js");
|
|
11
|
+
Object.defineProperty(exports, "getDistance", { enumerable: true, get: function () { return distance_js_1.getDistance; } });
|
|
12
|
+
const route_js_1 = require("./route.js");
|
|
13
|
+
Object.defineProperty(exports, "optimizeRoute", { enumerable: true, get: function () { return route_js_1.optimizeRoute; } });
|
|
14
|
+
Object.defineProperty(exports, "ROUTE_OPTIMIZATION", { enumerable: true, get: function () { return route_js_1.ROUTE_OPTIMIZATION; } });
|
|
10
15
|
const country_js_1 = require("./country.js");
|
|
11
16
|
Object.defineProperty(exports, "getCountries", { enumerable: true, get: function () { return country_js_1.getCountries; } });
|
|
12
17
|
Object.defineProperty(exports, "getCountry", { enumerable: true, get: function () { return country_js_1.getCountry; } });
|
|
@@ -20,6 +25,9 @@ exports.zap = Object.freeze({
|
|
|
20
25
|
generateAddress: address_js_1.generateAddress,
|
|
21
26
|
generateLegacyAddress: legacy_address_js_1.generateLegacyAddress,
|
|
22
27
|
getDirectionsUrl: directions_js_1.getDirectionsUrl,
|
|
28
|
+
getDistance: distance_js_1.getDistance,
|
|
29
|
+
optimizeRoute: route_js_1.optimizeRoute,
|
|
30
|
+
routeOptimization: route_js_1.ROUTE_OPTIMIZATION,
|
|
23
31
|
getCountries: country_js_1.getCountries,
|
|
24
32
|
getCountry: country_js_1.getCountry,
|
|
25
33
|
getProvinces: country_js_1.getProvinces,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AddressResolver, Coordinates, LocationInput, LocationPoint, SavedLocationPoint } from "./types.js";
|
|
2
|
+
export declare function validateLocationOptions(options: unknown): void;
|
|
3
|
+
/** Read original GPS only; never parse text, grid blocks, unit numbers, or suffixes. */
|
|
4
|
+
export declare function getLocationCoordinates(point: LocationPoint): Coordinates;
|
|
5
|
+
export declare function resolveLocationPoints(points: readonly LocationInput[], resolver: AddressResolver | undefined): LocationPoint[] | Promise<LocationPoint[]>;
|
|
6
|
+
export type ProjectedPoint = SavedLocationPoint & Readonly<{
|
|
7
|
+
gridId: string;
|
|
8
|
+
northingMeters: number;
|
|
9
|
+
eastingMeters: number;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function getProjectedPoint(point: LocationPoint): ProjectedPoint;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateLocationOptions = validateLocationOptions;
|
|
4
|
+
exports.getLocationCoordinates = getLocationCoordinates;
|
|
5
|
+
exports.resolveLocationPoints = resolveLocationPoints;
|
|
6
|
+
exports.getProjectedPoint = getProjectedPoint;
|
|
7
|
+
const grid_js_1 = require("./grid.js");
|
|
8
|
+
function validateLocationOptions(options) {
|
|
9
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
10
|
+
throw new TypeError("Options must be an object.");
|
|
11
|
+
}
|
|
12
|
+
if ("resolveAddress" in options && options.resolveAddress !== undefined && typeof options.resolveAddress !== "function") {
|
|
13
|
+
throw new TypeError("resolveAddress must be a function.");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Read original GPS only; never parse text, grid blocks, unit numbers, or suffixes. */
|
|
17
|
+
function getLocationCoordinates(point) {
|
|
18
|
+
if (!point || typeof point !== "object" || Array.isArray(point)) {
|
|
19
|
+
throw new TypeError("A latitude/longitude object or a saved record with coordinates is required.");
|
|
20
|
+
}
|
|
21
|
+
const coordinates = "coordinates" in point ? point.coordinates : point;
|
|
22
|
+
if (!coordinates || typeof coordinates !== "object" || Array.isArray(coordinates)) {
|
|
23
|
+
throw new TypeError("A saved record must contain its original GPS coordinates.");
|
|
24
|
+
}
|
|
25
|
+
(0, grid_js_1.validateCoordinates)(coordinates);
|
|
26
|
+
return { latitude: coordinates.latitude, longitude: coordinates.longitude };
|
|
27
|
+
}
|
|
28
|
+
function validateResolvedRecord(record) {
|
|
29
|
+
if (!record || typeof record !== "object" || Array.isArray(record) || !("coordinates" in record)) {
|
|
30
|
+
throw new TypeError("resolveAddress must return a saved record with original GPS coordinates.");
|
|
31
|
+
}
|
|
32
|
+
getLocationCoordinates(record);
|
|
33
|
+
return record;
|
|
34
|
+
}
|
|
35
|
+
function resolveLocationPoints(points, resolver) {
|
|
36
|
+
const checkString = (point) => {
|
|
37
|
+
if (!point.trim())
|
|
38
|
+
throw new TypeError("An address string must be nonempty.");
|
|
39
|
+
};
|
|
40
|
+
if (resolver !== undefined) {
|
|
41
|
+
// Promise.all installs rejection handlers for every asynchronous lookup.
|
|
42
|
+
return Promise.all(points.map(async (point, originalIndex) => {
|
|
43
|
+
if (typeof point === "string") {
|
|
44
|
+
checkString(point);
|
|
45
|
+
return validateResolvedRecord(await resolver(point, originalIndex));
|
|
46
|
+
}
|
|
47
|
+
getLocationCoordinates(point);
|
|
48
|
+
return point;
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
return points.map(point => {
|
|
52
|
+
if (typeof point === "string") {
|
|
53
|
+
checkString(point);
|
|
54
|
+
throw new TypeError("Address strings require resolveAddress; ZAP has no reverse decoder or automatic geocoder.");
|
|
55
|
+
}
|
|
56
|
+
getLocationCoordinates(point);
|
|
57
|
+
return point;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function getProjectedPoint(point) {
|
|
61
|
+
if (!("coordinates" in point) || !("gridId" in point) || typeof point.gridId !== "string" || !point.gridId.trim()
|
|
62
|
+
|| !("northingMeters" in point) || !Number.isFinite(point.northingMeters)
|
|
63
|
+
|| !("eastingMeters" in point) || !Number.isFinite(point.eastingMeters)) {
|
|
64
|
+
throw new TypeError("Projected distance requires saved records with a gridId and finite northingMeters/eastingMeters.");
|
|
65
|
+
}
|
|
66
|
+
return point;
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LocationInput, LocationPoint, ResolvedRouteOptions, RouteOptions, RouteResult } from "./types.js";
|
|
2
|
+
/** Work limits are separate from address protocol/configuration versions. */
|
|
3
|
+
export declare const ROUTE_OPTIMIZATION: Readonly<{
|
|
4
|
+
readonly exactMaxStops: 12;
|
|
5
|
+
readonly maxStops: 1000;
|
|
6
|
+
readonly twoOptMaxPasses: 100;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function optimizeRoute<T extends LocationPoint>(points: readonly T[], options?: RouteOptions): RouteResult<T>;
|
|
9
|
+
export declare function optimizeRoute<T extends LocationInput>(points: readonly T[], options: ResolvedRouteOptions): Promise<RouteResult<T>>;
|
|
10
|
+
export declare function optimizeRoute<T extends LocationPoint>(points: readonly T[], options: RouteOptions | ResolvedRouteOptions): RouteResult<T> | Promise<RouteResult<T>>;
|