@hpcc-js/map 3.5.4 → 3.5.6
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 +43 -43
- package/README.md +88 -88
- package/TopoJSON/BR.json +122 -122
- package/TopoJSON/GB_idx.json +1 -1
- package/TopoJSON/IE_idx.json +1 -1
- package/TopoJSON/ND_idx.json +1 -1
- package/TopoJSON/countries.json +257 -257
- package/TopoJSON/us-counties.json +16550 -16550
- package/TopoJSON/us-states.json +458 -458
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +1 -1
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +12 -10
- package/src/CanvasPinLayer.ts +99 -99
- package/src/CanvasPins.ts +397 -397
- package/src/Choropleth.css +26 -26
- package/src/Choropleth.ts +203 -203
- package/src/ChoroplethContinents.ts +13 -13
- package/src/ChoroplethCounties.ts +111 -111
- package/src/ChoroplethCountries.ts +100 -100
- package/src/ChoroplethStates.ts +103 -103
- package/src/ChoroplethStatesHeat.ts +8 -8
- package/src/GMap.css +20 -20
- package/src/GMap.ts +880 -880
- package/src/GMapCounties.ts +93 -93
- package/src/GMapGraph.ts +61 -61
- package/src/GMapHeat.ts +27 -27
- package/src/GMapLayered.ts +94 -94
- package/src/GMapPin.ts +115 -115
- package/src/GMapPinLine.ts +138 -138
- package/src/GeoHash.css +14 -14
- package/src/GeoHash.ts +139 -139
- package/src/Graph.css +9 -9
- package/src/Graph.ts +98 -98
- package/src/Graticule.css +12 -12
- package/src/Graticule.ts +97 -97
- package/src/Heat.ts +87 -87
- package/src/IChoropleth.ts +8 -8
- package/src/Layer.ts +99 -99
- package/src/Layered.css +18 -18
- package/src/Layered.ts +206 -206
- package/src/Lines.css +8 -8
- package/src/Lines.ts +78 -78
- package/src/OpenStreet.css +15 -15
- package/src/OpenStreet.ts +126 -126
- package/src/Pins.css +17 -17
- package/src/Pins.ts +350 -350
- package/src/Projection.ts +42 -42
- package/src/TestHeatMap.ts +8 -8
- package/src/TopoJSONChoropleth.ts +125 -125
- package/src/Utility.ts +484 -484
- package/src/__package__.ts +3 -3
- package/src/index.ts +33 -33
- package/src/leaflet/AlbersPR.ts +48 -48
- package/src/leaflet/Blank.ts +9 -9
- package/src/leaflet/Circles.ts +139 -139
- package/src/leaflet/ClusterCircles.css +26 -26
- package/src/leaflet/ClusterCircles.ts +88 -88
- package/src/leaflet/Countries.ts +43 -43
- package/src/leaflet/DrawLayer.ts +167 -167
- package/src/leaflet/FeatureLayer.ts +138 -138
- package/src/leaflet/GMap.ts +44 -44
- package/src/leaflet/HeatLayer.ts +77 -77
- package/src/leaflet/Icons.ts +60 -60
- package/src/leaflet/Leaflet.css +3 -3
- package/src/leaflet/Leaflet.ts +239 -239
- package/src/leaflet/MapBox.ts +35 -35
- package/src/leaflet/Markers.ts +109 -109
- package/src/leaflet/OpenStreet.ts +27 -27
- package/src/leaflet/Path.ts +138 -138
- package/src/leaflet/Pins.ts +73 -73
- package/src/leaflet/Polygons.ts +113 -113
- package/src/leaflet/Region.ts +138 -138
- package/src/leaflet/Text.ts +99 -99
- package/src/leaflet/TileLayer.ts +81 -81
- package/src/leaflet/TopoJSON.ts +146 -146
- package/src/leaflet/US.ts +15 -15
- package/src/leaflet/USCounties.ts +43 -43
- package/src/leaflet/USStates.ts +41 -41
- package/src/leaflet/World.css +3 -3
- package/src/leaflet/World.ts +172 -172
- package/src/leaflet/index.ts +18 -18
- package/src/leaflet/leaflet-shim.ts +18 -18
- package/src/leaflet/plugins/BeautifyIcon.css +44 -44
- package/src/leaflet/plugins/BeautifyIcon.ts +190 -190
- package/src/leaflet/plugins/BeutifyIcon.licence +20 -20
- package/src/leaflet/plugins/D3SvgOverlay.css +2 -2
- package/src/leaflet/plugins/D3SvgOverlay.licence +20 -20
- package/src/leaflet/plugins/D3SvgOverlay.ts +175 -175
- package/src/leaflet/plugins/HeatLayer.license +21 -21
- package/src/leaflet/plugins/HeatLayer.ts +224 -224
- package/src/leaflet/plugins/Leaflet.GoogleMutant.ts +424 -424
- package/src/leaflet/plugins/lru_map.ts +352 -352
- package/src/test.ts +114 -114
package/src/Utility.ts
CHANGED
|
@@ -1,484 +1,484 @@
|
|
|
1
|
-
import { geoAlbers, geoConicEqualArea, GeoStream, geoStream } from "d3-geo";
|
|
2
|
-
|
|
3
|
-
function noop() { }
|
|
4
|
-
let x0 = Infinity;
|
|
5
|
-
let y0 = x0;
|
|
6
|
-
let x1 = -x0;
|
|
7
|
-
let y1 = x1;
|
|
8
|
-
|
|
9
|
-
function boundsPoint(x, y) {
|
|
10
|
-
if (x < x0) x0 = x;
|
|
11
|
-
if (x > x1) x1 = x;
|
|
12
|
-
if (y < y0) y0 = y;
|
|
13
|
-
if (y > y1) y1 = y;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const boundsStream = {
|
|
17
|
-
point: boundsPoint,
|
|
18
|
-
lineStart: noop,
|
|
19
|
-
lineEnd: noop,
|
|
20
|
-
polygonStart: noop,
|
|
21
|
-
polygonEnd: noop,
|
|
22
|
-
result() {
|
|
23
|
-
const bounds = [[x0, y0], [x1, y1]];
|
|
24
|
-
x1 = y1 = -(y0 = x0 = Infinity);
|
|
25
|
-
return bounds;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function fitExtent(projection, extent, object) {
|
|
30
|
-
const w = extent[1][0] - extent[0][0];
|
|
31
|
-
const h = extent[1][1] - extent[0][1];
|
|
32
|
-
const clip = projection.clipExtent && projection.clipExtent();
|
|
33
|
-
|
|
34
|
-
projection
|
|
35
|
-
.scale(150)
|
|
36
|
-
.translate([0, 0]);
|
|
37
|
-
|
|
38
|
-
if (clip != null) projection.clipExtent(null);
|
|
39
|
-
|
|
40
|
-
geoStream(object, projection.stream(boundsStream));
|
|
41
|
-
|
|
42
|
-
const b = boundsStream.result();
|
|
43
|
-
const k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1]));
|
|
44
|
-
const x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2;
|
|
45
|
-
const y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2;
|
|
46
|
-
|
|
47
|
-
if (clip != null) projection.clipExtent(clip);
|
|
48
|
-
|
|
49
|
-
return projection
|
|
50
|
-
.scale(k * 150)
|
|
51
|
-
.translate([x, y]);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function fitSize(projection, size, object) {
|
|
55
|
-
return fitExtent(projection, [[0, 0], size], object);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// const d3Geo = _d3Geo.geo || _d3Geo.default || _d3Geo;
|
|
59
|
-
|
|
60
|
-
// Origonally ased on geohash.js
|
|
61
|
-
// Geohash library for Javascript
|
|
62
|
-
// (c) 2008 David Troy
|
|
63
|
-
// Distributed under the MIT License
|
|
64
|
-
|
|
65
|
-
export class Geohash {
|
|
66
|
-
/* (Geohash-specific) Base32 map */
|
|
67
|
-
private static base32 = "0123456789bcdefghjkmnpqrstuvwxyz";
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Encodes latitude/longitude to geohash, either to specified precision or to automatically
|
|
71
|
-
* evaluated precision.
|
|
72
|
-
*
|
|
73
|
-
* @param {number} lat - Latitude in degrees.
|
|
74
|
-
* @param {number} lon - Longitude in degrees.
|
|
75
|
-
* @param {number} [precision] - Number of characters in resulting geohash.
|
|
76
|
-
* @returns {string} Geohash of supplied latitude/longitude.
|
|
77
|
-
* @throws Invalid geohash.
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* var geohash = Geohash.encode(52.205, 0.119, 7); // geohash: "u120fxw"
|
|
81
|
-
*/
|
|
82
|
-
encode(lat, lon, precision) {
|
|
83
|
-
// infer precision?
|
|
84
|
-
if (typeof precision === "undefined") {
|
|
85
|
-
// refine geohash until it matches precision of supplied lat/lon
|
|
86
|
-
for (let p = 1; p <= 12; p++) {
|
|
87
|
-
const hash = this.encode(lat, lon, p);
|
|
88
|
-
const posn = this.decode(hash);
|
|
89
|
-
if (posn.lat === lat && posn.lon === lon) return hash;
|
|
90
|
-
}
|
|
91
|
-
precision = 12; // set to maximum
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
lat = Number(lat);
|
|
95
|
-
lon = Number(lon);
|
|
96
|
-
precision = Number(precision);
|
|
97
|
-
|
|
98
|
-
if (isNaN(lat) || isNaN(lon) || isNaN(precision)) throw new Error("Invalid geohash");
|
|
99
|
-
|
|
100
|
-
let idx = 0; // index into base32 map
|
|
101
|
-
let bit = 0; // each char holds 5 bits
|
|
102
|
-
let evenBit = true;
|
|
103
|
-
let geohash = "";
|
|
104
|
-
|
|
105
|
-
let latMin = -90;
|
|
106
|
-
let latMax = 90;
|
|
107
|
-
let lonMin = -180;
|
|
108
|
-
let lonMax = 180;
|
|
109
|
-
|
|
110
|
-
while (geohash.length < precision) {
|
|
111
|
-
if (evenBit) {
|
|
112
|
-
// bisect E-W longitude
|
|
113
|
-
const lonMid = (lonMin + lonMax) / 2;
|
|
114
|
-
if (lon > lonMid) {
|
|
115
|
-
idx = idx * 2 + 1;
|
|
116
|
-
lonMin = lonMid;
|
|
117
|
-
} else {
|
|
118
|
-
idx = idx * 2;
|
|
119
|
-
lonMax = lonMid;
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
// bisect N-S latitude
|
|
123
|
-
const latMid = (latMin + latMax) / 2;
|
|
124
|
-
if (lat > latMid) {
|
|
125
|
-
idx = idx * 2 + 1;
|
|
126
|
-
latMin = latMid;
|
|
127
|
-
} else {
|
|
128
|
-
idx = idx * 2;
|
|
129
|
-
latMax = latMid;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
evenBit = !evenBit;
|
|
133
|
-
|
|
134
|
-
if (++bit === 5) {
|
|
135
|
-
// 5 bits gives us a character: append it and start over
|
|
136
|
-
geohash += Geohash.base32.charAt(idx);
|
|
137
|
-
bit = 0;
|
|
138
|
-
idx = 0;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return geohash;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Decode geohash to latitude/longitude (location is approximate centre of geohash cell,
|
|
147
|
-
* to reasonable precision).
|
|
148
|
-
*
|
|
149
|
-
* @param {string} geohash - Geohash string to be converted to latitude/longitude.
|
|
150
|
-
* @returns {{lat:number, lon:number}} (Center of) geohashed location.
|
|
151
|
-
* @throws Invalid geohash.
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* var latlon = Geohash.decode("u120fxw"); // latlon: { lat: 52.205, lon: 0.1188 }
|
|
155
|
-
*/
|
|
156
|
-
decode(geohash) {
|
|
157
|
-
|
|
158
|
-
const bounds = this.bounds(geohash); // <-- the hard work
|
|
159
|
-
// now just determine the centre of the cell...
|
|
160
|
-
|
|
161
|
-
const latMin = bounds.sw.lat;
|
|
162
|
-
const lonMin = bounds.sw.lon;
|
|
163
|
-
const latMax = bounds.ne.lat;
|
|
164
|
-
const lonMax = bounds.ne.lon;
|
|
165
|
-
|
|
166
|
-
// cell centre
|
|
167
|
-
let lat = (latMin + latMax) / 2;
|
|
168
|
-
let lon = (lonMin + lonMax) / 2;
|
|
169
|
-
|
|
170
|
-
// round to close to centre without excessive precision: ⌊2-log10(Δ°)⌋ decimal places
|
|
171
|
-
lat = Number(lat.toFixed(Math.floor(2 - Math.log(latMax - latMin) / Math.LN10)));
|
|
172
|
-
lon = Number(lon.toFixed(Math.floor(2 - Math.log(lonMax - lonMin) / Math.LN10)));
|
|
173
|
-
|
|
174
|
-
return { lat, lon };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Returns SW/NE latitude/longitude bounds of specified geohash.
|
|
179
|
-
*
|
|
180
|
-
* @param {string} geohash - Cell that bounds are required of.
|
|
181
|
-
* @returns {{sw: {lat: number, lon: number}, ne: {lat: number, lon: number}}}
|
|
182
|
-
* @throws Invalid geohash.
|
|
183
|
-
*/
|
|
184
|
-
bounds(geohash) {
|
|
185
|
-
if (geohash.length === 0) throw new Error("Invalid geohash");
|
|
186
|
-
|
|
187
|
-
geohash = geohash.toLowerCase();
|
|
188
|
-
|
|
189
|
-
let evenBit = true;
|
|
190
|
-
let latMin = -90;
|
|
191
|
-
let latMax = 90;
|
|
192
|
-
let lonMin = -180;
|
|
193
|
-
let lonMax = 180;
|
|
194
|
-
|
|
195
|
-
for (let i = 0; i < geohash.length; i++) {
|
|
196
|
-
const chr = geohash.charAt(i);
|
|
197
|
-
const idx = Geohash.base32.indexOf(chr);
|
|
198
|
-
if (idx === -1) throw new Error("Invalid geohash");
|
|
199
|
-
|
|
200
|
-
for (let n = 4; n >= 0; n--) {
|
|
201
|
-
const bitN = idx >> n & 1;
|
|
202
|
-
if (evenBit) {
|
|
203
|
-
// longitude
|
|
204
|
-
const lonMid = (lonMin + lonMax) / 2;
|
|
205
|
-
if (bitN === 1) {
|
|
206
|
-
lonMin = lonMid;
|
|
207
|
-
} else {
|
|
208
|
-
lonMax = lonMid;
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
// latitude
|
|
212
|
-
const latMid = (latMin + latMax) / 2;
|
|
213
|
-
if (bitN === 1) {
|
|
214
|
-
latMin = latMid;
|
|
215
|
-
} else {
|
|
216
|
-
latMax = latMid;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
evenBit = !evenBit;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const bounds = {
|
|
224
|
-
sw: { lat: latMin, lon: lonMin },
|
|
225
|
-
ne: { lat: latMax, lon: lonMax }
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
return bounds;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Determines adjacent cell in given direction.
|
|
233
|
-
*
|
|
234
|
-
* @param geohash - Cell to which adjacent cell is required.
|
|
235
|
-
* @param direction - Direction from geohash (N/S/E/W).
|
|
236
|
-
* @returns {string} Geocode of adjacent cell.
|
|
237
|
-
* @throws Invalid geohash.
|
|
238
|
-
*/
|
|
239
|
-
adjacent(geohash, direction) {
|
|
240
|
-
// based on github.com/davetroy/geohash-js
|
|
241
|
-
|
|
242
|
-
geohash = geohash.toLowerCase();
|
|
243
|
-
direction = direction.toLowerCase();
|
|
244
|
-
|
|
245
|
-
if (geohash.length === 0) throw new Error("Invalid geohash");
|
|
246
|
-
if ("nsew".indexOf(direction) === -1) throw new Error("Invalid direction");
|
|
247
|
-
|
|
248
|
-
const neighbour = {
|
|
249
|
-
n: ["p0r21436x8zb9dcf5h7kjnmqesgutwvy", "bc01fg45238967deuvhjyznpkmstqrwx"],
|
|
250
|
-
s: ["14365h7k9dcfesgujnmqp0r2twvyx8zb", "238967debc01fg45kmstqrwxuvhjyznp"],
|
|
251
|
-
e: ["bc01fg45238967deuvhjyznpkmstqrwx", "p0r21436x8zb9dcf5h7kjnmqesgutwvy"],
|
|
252
|
-
w: ["238967debc01fg45kmstqrwxuvhjyznp", "14365h7k9dcfesgujnmqp0r2twvyx8zb"]
|
|
253
|
-
};
|
|
254
|
-
const border = {
|
|
255
|
-
n: ["prxz", "bcfguvyz"],
|
|
256
|
-
s: ["028b", "0145hjnp"],
|
|
257
|
-
e: ["bcfguvyz", "prxz"],
|
|
258
|
-
w: ["0145hjnp", "028b"]
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
const lastCh = geohash.slice(-1); // last character of hash
|
|
262
|
-
let parent = geohash.slice(0, -1); // hash without last character
|
|
263
|
-
|
|
264
|
-
const type = geohash.length % 2;
|
|
265
|
-
|
|
266
|
-
// check for edge-cases which don"t share common prefix
|
|
267
|
-
if (border[direction][type].indexOf(lastCh) !== -1 && parent !== "") {
|
|
268
|
-
parent = this.adjacent(parent, direction);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// append letter for direction to parent
|
|
272
|
-
return parent + Geohash.base32.charAt(neighbour[direction][type].indexOf(lastCh));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Returns all 8 adjacent cells to specified geohash.
|
|
277
|
-
*
|
|
278
|
-
* @param {string} geohash - Geohash neighbours are required of.
|
|
279
|
-
* @returns {{n,ne,e,se,s,sw,w,nw: string}}
|
|
280
|
-
* @throws Invalid geohash.
|
|
281
|
-
*/
|
|
282
|
-
neighbours(geohash) {
|
|
283
|
-
return {
|
|
284
|
-
n: this.adjacent(geohash, "n"),
|
|
285
|
-
ne: this.adjacent(this.adjacent(geohash, "n"), "e"),
|
|
286
|
-
e: this.adjacent(geohash, "e"),
|
|
287
|
-
se: this.adjacent(this.adjacent(geohash, "s"), "e"),
|
|
288
|
-
s: this.adjacent(geohash, "s"),
|
|
289
|
-
sw: this.adjacent(this.adjacent(geohash, "s"), "w"),
|
|
290
|
-
w: this.adjacent(geohash, "w"),
|
|
291
|
-
nw: this.adjacent(this.adjacent(geohash, "n"), "w")
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// HPCC Extensions ---
|
|
296
|
-
contained(w, n, e, s, precision) {
|
|
297
|
-
if (isNaN(n) || n >= 90) n = 89;
|
|
298
|
-
if (isNaN(e) || e > 180) e = 180;
|
|
299
|
-
if (isNaN(s) || s <= -90) s = -89;
|
|
300
|
-
if (isNaN(w) || w < -180) w = -180;
|
|
301
|
-
precision = precision || 1;
|
|
302
|
-
const geoHashNW = this.encode(n, w, precision);
|
|
303
|
-
const geoHashNE = this.encode(n, e, precision);
|
|
304
|
-
const geoHashSE = this.encode(s, e, precision);
|
|
305
|
-
let currRowHash = geoHashNW;
|
|
306
|
-
let col = 0;
|
|
307
|
-
let maxCol = -1;
|
|
308
|
-
const geoHashes = [geoHashNW, geoHashSE];
|
|
309
|
-
let currHash = this.adjacent(geoHashNW, "e");
|
|
310
|
-
while (currHash !== geoHashSE) {
|
|
311
|
-
geoHashes.push(currHash);
|
|
312
|
-
++col;
|
|
313
|
-
if (currHash === geoHashNE || maxCol === col) {
|
|
314
|
-
maxCol = col + 1;
|
|
315
|
-
col = 0;
|
|
316
|
-
currHash = this.adjacent(currRowHash, "s");
|
|
317
|
-
currRowHash = currHash;
|
|
318
|
-
} else {
|
|
319
|
-
currHash = this.adjacent(currHash, "e");
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return geoHashes;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
calculateWidthDegrees(n) {
|
|
326
|
-
let a;
|
|
327
|
-
if (n % 2 === 0) {
|
|
328
|
-
a = -1;
|
|
329
|
-
} else {
|
|
330
|
-
a = -0.5;
|
|
331
|
-
}
|
|
332
|
-
const result = 180 / Math.pow(2, 2.5 * n + a);
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
width(n) {
|
|
337
|
-
const parity = n % 2;
|
|
338
|
-
return 180 / (2 ^ (((5 * n + parity) / 2) - 1));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function multiplex(streams) {
|
|
344
|
-
const n = streams.length;
|
|
345
|
-
return {
|
|
346
|
-
point(x, y) { let i = -1; while (++i < n) streams[i].point(x, y); },
|
|
347
|
-
sphere() { let i = -1; while (++i < n) streams[i].sphere(); },
|
|
348
|
-
lineStart() { let i = -1; while (++i < n) streams[i].lineStart(); },
|
|
349
|
-
lineEnd() { let i = -1; while (++i < n) streams[i].lineEnd(); },
|
|
350
|
-
polygonStart() { let i = -1; while (++i < n) streams[i].polygonStart(); },
|
|
351
|
-
polygonEnd() { let i = -1; while (++i < n) streams[i].polygonEnd(); }
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// A modified d3.geo.albersUsa to include Puerto Rico.
|
|
356
|
-
export function albersUsaPr() {
|
|
357
|
-
let cache;
|
|
358
|
-
let cacheStream;
|
|
359
|
-
|
|
360
|
-
const ε = 1e-6;
|
|
361
|
-
|
|
362
|
-
const lower48 = geoAlbers();
|
|
363
|
-
let lower48Point;
|
|
364
|
-
|
|
365
|
-
// EPSG:3338
|
|
366
|
-
const alaska = geoConicEqualArea()
|
|
367
|
-
.rotate([154, 0])
|
|
368
|
-
.center([-2, 58.5])
|
|
369
|
-
.parallels([55, 65]);
|
|
370
|
-
let alaskaPoint;
|
|
371
|
-
|
|
372
|
-
// ESRI:102007
|
|
373
|
-
const hawaii = geoConicEqualArea()
|
|
374
|
-
.rotate([157, 0])
|
|
375
|
-
.center([-3, 19.9])
|
|
376
|
-
.parallels([8, 18]);
|
|
377
|
-
let hawaiiPoint;
|
|
378
|
-
|
|
379
|
-
// XXX? You should check that this is a standard PR projection!
|
|
380
|
-
const puertoRico = geoConicEqualArea()
|
|
381
|
-
.rotate([66, 0])
|
|
382
|
-
.center([0, 18])
|
|
383
|
-
.parallels([8, 18]);
|
|
384
|
-
let puertoRicoPoint;
|
|
385
|
-
|
|
386
|
-
let point;
|
|
387
|
-
const pointStream = { point: (x, y) => { point = [x, y]; } } as GeoStream;
|
|
388
|
-
|
|
389
|
-
const albersUsa: any = function (coordinates) {
|
|
390
|
-
const x = coordinates[0];
|
|
391
|
-
const y = coordinates[1];
|
|
392
|
-
point = null;
|
|
393
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
394
|
-
(lower48Point.point(x, y), point) ||
|
|
395
|
-
(alaskaPoint.point(x, y), point) ||
|
|
396
|
-
(hawaiiPoint.point(x, y), point) ||
|
|
397
|
-
(puertoRicoPoint.point(x, y), point);
|
|
398
|
-
return point;
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
albersUsa.invert = function (coordinates) {
|
|
402
|
-
const k = lower48.scale();
|
|
403
|
-
const t = lower48.translate();
|
|
404
|
-
const x = (coordinates[0] - t[0]) / k;
|
|
405
|
-
const y = (coordinates[1] - t[1]) / k;
|
|
406
|
-
return (y >= 0.120 && y < 0.234 && x >= -0.425 && x < -0.214 ? alaska
|
|
407
|
-
: y >= 0.166 && y < 0.234 && x >= -0.214 && x < -0.115 ? hawaii
|
|
408
|
-
: y >= 0.204 && y < 0.234 && x >= 0.320 && x < 0.380 ? puertoRico
|
|
409
|
-
: lower48).invert(coordinates);
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
// A naïve multi-projection stream.
|
|
413
|
-
// The projections must have mutually exclusive clip regions on the sphere,
|
|
414
|
-
// as this will avoid emitting interleaving lines and polygons.
|
|
415
|
-
albersUsa.stream = function (stream) {
|
|
416
|
-
return cache && cacheStream === stream ? cache : cache = multiplex([
|
|
417
|
-
lower48.stream(cacheStream = stream),
|
|
418
|
-
alaska.stream(stream),
|
|
419
|
-
hawaii.stream(stream),
|
|
420
|
-
puertoRico.stream(stream)
|
|
421
|
-
]);
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
albersUsa.precision = function (_) {
|
|
425
|
-
if (!arguments.length) return lower48.precision();
|
|
426
|
-
lower48.precision(_);
|
|
427
|
-
alaska.precision(_);
|
|
428
|
-
hawaii.precision(_);
|
|
429
|
-
puertoRico.precision(_);
|
|
430
|
-
return reset();
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
albersUsa.scale = function (_) {
|
|
434
|
-
if (!arguments.length) return lower48.scale();
|
|
435
|
-
lower48.scale(_);
|
|
436
|
-
alaska.scale(_ * 0.35);
|
|
437
|
-
hawaii.scale(_);
|
|
438
|
-
puertoRico.scale(_);
|
|
439
|
-
return albersUsa.translate(lower48.translate());
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
albersUsa.translate = function (_) {
|
|
443
|
-
if (!arguments.length) return lower48.translate();
|
|
444
|
-
const k = lower48.scale();
|
|
445
|
-
const x = +_[0];
|
|
446
|
-
const y = +_[1];
|
|
447
|
-
|
|
448
|
-
lower48Point = lower48
|
|
449
|
-
.translate(_)
|
|
450
|
-
.clipExtent([[x - 0.455 * k, y - 0.238 * k], [x + 0.455 * k, y + 0.238 * k]])
|
|
451
|
-
.stream(pointStream);
|
|
452
|
-
|
|
453
|
-
alaskaPoint = alaska
|
|
454
|
-
.translate([x - 0.307 * k, y + 0.201 * k])
|
|
455
|
-
.clipExtent([[x - 0.425 * k + ε, y + 0.120 * k + ε], [x - 0.214 * k - ε, y + 0.234 * k - ε]])
|
|
456
|
-
.stream(pointStream);
|
|
457
|
-
|
|
458
|
-
hawaiiPoint = hawaii
|
|
459
|
-
.translate([x - 0.205 * k, y + 0.212 * k])
|
|
460
|
-
.clipExtent([[x - 0.214 * k + ε, y + 0.166 * k + ε], [x - 0.115 * k - ε, y + 0.234 * k - ε]])
|
|
461
|
-
.stream(pointStream);
|
|
462
|
-
|
|
463
|
-
puertoRicoPoint = puertoRico
|
|
464
|
-
.translate([x + 0.350 * k, y + 0.224 * k])
|
|
465
|
-
.clipExtent([[x + 0.320 * k, y + 0.204 * k], [x + 0.380 * k, y + 0.234 * k]])
|
|
466
|
-
.stream(pointStream);
|
|
467
|
-
|
|
468
|
-
return reset();
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
albersUsa.fitExtent = function (extent, object) {
|
|
472
|
-
return fitExtent(albersUsa, extent, object);
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
albersUsa.fitSize = function (size, object) {
|
|
476
|
-
return fitSize(albersUsa, size, object);
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
function reset() {
|
|
480
|
-
cache = cacheStream = null;
|
|
481
|
-
return albersUsa;
|
|
482
|
-
}
|
|
483
|
-
return albersUsa.scale(1070);
|
|
484
|
-
}
|
|
1
|
+
import { geoAlbers, geoConicEqualArea, GeoStream, geoStream } from "d3-geo";
|
|
2
|
+
|
|
3
|
+
function noop() { }
|
|
4
|
+
let x0 = Infinity;
|
|
5
|
+
let y0 = x0;
|
|
6
|
+
let x1 = -x0;
|
|
7
|
+
let y1 = x1;
|
|
8
|
+
|
|
9
|
+
function boundsPoint(x, y) {
|
|
10
|
+
if (x < x0) x0 = x;
|
|
11
|
+
if (x > x1) x1 = x;
|
|
12
|
+
if (y < y0) y0 = y;
|
|
13
|
+
if (y > y1) y1 = y;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const boundsStream = {
|
|
17
|
+
point: boundsPoint,
|
|
18
|
+
lineStart: noop,
|
|
19
|
+
lineEnd: noop,
|
|
20
|
+
polygonStart: noop,
|
|
21
|
+
polygonEnd: noop,
|
|
22
|
+
result() {
|
|
23
|
+
const bounds = [[x0, y0], [x1, y1]];
|
|
24
|
+
x1 = y1 = -(y0 = x0 = Infinity);
|
|
25
|
+
return bounds;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function fitExtent(projection, extent, object) {
|
|
30
|
+
const w = extent[1][0] - extent[0][0];
|
|
31
|
+
const h = extent[1][1] - extent[0][1];
|
|
32
|
+
const clip = projection.clipExtent && projection.clipExtent();
|
|
33
|
+
|
|
34
|
+
projection
|
|
35
|
+
.scale(150)
|
|
36
|
+
.translate([0, 0]);
|
|
37
|
+
|
|
38
|
+
if (clip != null) projection.clipExtent(null);
|
|
39
|
+
|
|
40
|
+
geoStream(object, projection.stream(boundsStream));
|
|
41
|
+
|
|
42
|
+
const b = boundsStream.result();
|
|
43
|
+
const k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1]));
|
|
44
|
+
const x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2;
|
|
45
|
+
const y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2;
|
|
46
|
+
|
|
47
|
+
if (clip != null) projection.clipExtent(clip);
|
|
48
|
+
|
|
49
|
+
return projection
|
|
50
|
+
.scale(k * 150)
|
|
51
|
+
.translate([x, y]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fitSize(projection, size, object) {
|
|
55
|
+
return fitExtent(projection, [[0, 0], size], object);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// const d3Geo = _d3Geo.geo || _d3Geo.default || _d3Geo;
|
|
59
|
+
|
|
60
|
+
// Origonally ased on geohash.js
|
|
61
|
+
// Geohash library for Javascript
|
|
62
|
+
// (c) 2008 David Troy
|
|
63
|
+
// Distributed under the MIT License
|
|
64
|
+
|
|
65
|
+
export class Geohash {
|
|
66
|
+
/* (Geohash-specific) Base32 map */
|
|
67
|
+
private static base32 = "0123456789bcdefghjkmnpqrstuvwxyz";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Encodes latitude/longitude to geohash, either to specified precision or to automatically
|
|
71
|
+
* evaluated precision.
|
|
72
|
+
*
|
|
73
|
+
* @param {number} lat - Latitude in degrees.
|
|
74
|
+
* @param {number} lon - Longitude in degrees.
|
|
75
|
+
* @param {number} [precision] - Number of characters in resulting geohash.
|
|
76
|
+
* @returns {string} Geohash of supplied latitude/longitude.
|
|
77
|
+
* @throws Invalid geohash.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* var geohash = Geohash.encode(52.205, 0.119, 7); // geohash: "u120fxw"
|
|
81
|
+
*/
|
|
82
|
+
encode(lat, lon, precision) {
|
|
83
|
+
// infer precision?
|
|
84
|
+
if (typeof precision === "undefined") {
|
|
85
|
+
// refine geohash until it matches precision of supplied lat/lon
|
|
86
|
+
for (let p = 1; p <= 12; p++) {
|
|
87
|
+
const hash = this.encode(lat, lon, p);
|
|
88
|
+
const posn = this.decode(hash);
|
|
89
|
+
if (posn.lat === lat && posn.lon === lon) return hash;
|
|
90
|
+
}
|
|
91
|
+
precision = 12; // set to maximum
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
lat = Number(lat);
|
|
95
|
+
lon = Number(lon);
|
|
96
|
+
precision = Number(precision);
|
|
97
|
+
|
|
98
|
+
if (isNaN(lat) || isNaN(lon) || isNaN(precision)) throw new Error("Invalid geohash");
|
|
99
|
+
|
|
100
|
+
let idx = 0; // index into base32 map
|
|
101
|
+
let bit = 0; // each char holds 5 bits
|
|
102
|
+
let evenBit = true;
|
|
103
|
+
let geohash = "";
|
|
104
|
+
|
|
105
|
+
let latMin = -90;
|
|
106
|
+
let latMax = 90;
|
|
107
|
+
let lonMin = -180;
|
|
108
|
+
let lonMax = 180;
|
|
109
|
+
|
|
110
|
+
while (geohash.length < precision) {
|
|
111
|
+
if (evenBit) {
|
|
112
|
+
// bisect E-W longitude
|
|
113
|
+
const lonMid = (lonMin + lonMax) / 2;
|
|
114
|
+
if (lon > lonMid) {
|
|
115
|
+
idx = idx * 2 + 1;
|
|
116
|
+
lonMin = lonMid;
|
|
117
|
+
} else {
|
|
118
|
+
idx = idx * 2;
|
|
119
|
+
lonMax = lonMid;
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// bisect N-S latitude
|
|
123
|
+
const latMid = (latMin + latMax) / 2;
|
|
124
|
+
if (lat > latMid) {
|
|
125
|
+
idx = idx * 2 + 1;
|
|
126
|
+
latMin = latMid;
|
|
127
|
+
} else {
|
|
128
|
+
idx = idx * 2;
|
|
129
|
+
latMax = latMid;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
evenBit = !evenBit;
|
|
133
|
+
|
|
134
|
+
if (++bit === 5) {
|
|
135
|
+
// 5 bits gives us a character: append it and start over
|
|
136
|
+
geohash += Geohash.base32.charAt(idx);
|
|
137
|
+
bit = 0;
|
|
138
|
+
idx = 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return geohash;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Decode geohash to latitude/longitude (location is approximate centre of geohash cell,
|
|
147
|
+
* to reasonable precision).
|
|
148
|
+
*
|
|
149
|
+
* @param {string} geohash - Geohash string to be converted to latitude/longitude.
|
|
150
|
+
* @returns {{lat:number, lon:number}} (Center of) geohashed location.
|
|
151
|
+
* @throws Invalid geohash.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* var latlon = Geohash.decode("u120fxw"); // latlon: { lat: 52.205, lon: 0.1188 }
|
|
155
|
+
*/
|
|
156
|
+
decode(geohash) {
|
|
157
|
+
|
|
158
|
+
const bounds = this.bounds(geohash); // <-- the hard work
|
|
159
|
+
// now just determine the centre of the cell...
|
|
160
|
+
|
|
161
|
+
const latMin = bounds.sw.lat;
|
|
162
|
+
const lonMin = bounds.sw.lon;
|
|
163
|
+
const latMax = bounds.ne.lat;
|
|
164
|
+
const lonMax = bounds.ne.lon;
|
|
165
|
+
|
|
166
|
+
// cell centre
|
|
167
|
+
let lat = (latMin + latMax) / 2;
|
|
168
|
+
let lon = (lonMin + lonMax) / 2;
|
|
169
|
+
|
|
170
|
+
// round to close to centre without excessive precision: ⌊2-log10(Δ°)⌋ decimal places
|
|
171
|
+
lat = Number(lat.toFixed(Math.floor(2 - Math.log(latMax - latMin) / Math.LN10)));
|
|
172
|
+
lon = Number(lon.toFixed(Math.floor(2 - Math.log(lonMax - lonMin) / Math.LN10)));
|
|
173
|
+
|
|
174
|
+
return { lat, lon };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Returns SW/NE latitude/longitude bounds of specified geohash.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} geohash - Cell that bounds are required of.
|
|
181
|
+
* @returns {{sw: {lat: number, lon: number}, ne: {lat: number, lon: number}}}
|
|
182
|
+
* @throws Invalid geohash.
|
|
183
|
+
*/
|
|
184
|
+
bounds(geohash) {
|
|
185
|
+
if (geohash.length === 0) throw new Error("Invalid geohash");
|
|
186
|
+
|
|
187
|
+
geohash = geohash.toLowerCase();
|
|
188
|
+
|
|
189
|
+
let evenBit = true;
|
|
190
|
+
let latMin = -90;
|
|
191
|
+
let latMax = 90;
|
|
192
|
+
let lonMin = -180;
|
|
193
|
+
let lonMax = 180;
|
|
194
|
+
|
|
195
|
+
for (let i = 0; i < geohash.length; i++) {
|
|
196
|
+
const chr = geohash.charAt(i);
|
|
197
|
+
const idx = Geohash.base32.indexOf(chr);
|
|
198
|
+
if (idx === -1) throw new Error("Invalid geohash");
|
|
199
|
+
|
|
200
|
+
for (let n = 4; n >= 0; n--) {
|
|
201
|
+
const bitN = idx >> n & 1;
|
|
202
|
+
if (evenBit) {
|
|
203
|
+
// longitude
|
|
204
|
+
const lonMid = (lonMin + lonMax) / 2;
|
|
205
|
+
if (bitN === 1) {
|
|
206
|
+
lonMin = lonMid;
|
|
207
|
+
} else {
|
|
208
|
+
lonMax = lonMid;
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
// latitude
|
|
212
|
+
const latMid = (latMin + latMax) / 2;
|
|
213
|
+
if (bitN === 1) {
|
|
214
|
+
latMin = latMid;
|
|
215
|
+
} else {
|
|
216
|
+
latMax = latMid;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
evenBit = !evenBit;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const bounds = {
|
|
224
|
+
sw: { lat: latMin, lon: lonMin },
|
|
225
|
+
ne: { lat: latMax, lon: lonMax }
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
return bounds;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Determines adjacent cell in given direction.
|
|
233
|
+
*
|
|
234
|
+
* @param geohash - Cell to which adjacent cell is required.
|
|
235
|
+
* @param direction - Direction from geohash (N/S/E/W).
|
|
236
|
+
* @returns {string} Geocode of adjacent cell.
|
|
237
|
+
* @throws Invalid geohash.
|
|
238
|
+
*/
|
|
239
|
+
adjacent(geohash, direction) {
|
|
240
|
+
// based on github.com/davetroy/geohash-js
|
|
241
|
+
|
|
242
|
+
geohash = geohash.toLowerCase();
|
|
243
|
+
direction = direction.toLowerCase();
|
|
244
|
+
|
|
245
|
+
if (geohash.length === 0) throw new Error("Invalid geohash");
|
|
246
|
+
if ("nsew".indexOf(direction) === -1) throw new Error("Invalid direction");
|
|
247
|
+
|
|
248
|
+
const neighbour = {
|
|
249
|
+
n: ["p0r21436x8zb9dcf5h7kjnmqesgutwvy", "bc01fg45238967deuvhjyznpkmstqrwx"],
|
|
250
|
+
s: ["14365h7k9dcfesgujnmqp0r2twvyx8zb", "238967debc01fg45kmstqrwxuvhjyznp"],
|
|
251
|
+
e: ["bc01fg45238967deuvhjyznpkmstqrwx", "p0r21436x8zb9dcf5h7kjnmqesgutwvy"],
|
|
252
|
+
w: ["238967debc01fg45kmstqrwxuvhjyznp", "14365h7k9dcfesgujnmqp0r2twvyx8zb"]
|
|
253
|
+
};
|
|
254
|
+
const border = {
|
|
255
|
+
n: ["prxz", "bcfguvyz"],
|
|
256
|
+
s: ["028b", "0145hjnp"],
|
|
257
|
+
e: ["bcfguvyz", "prxz"],
|
|
258
|
+
w: ["0145hjnp", "028b"]
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const lastCh = geohash.slice(-1); // last character of hash
|
|
262
|
+
let parent = geohash.slice(0, -1); // hash without last character
|
|
263
|
+
|
|
264
|
+
const type = geohash.length % 2;
|
|
265
|
+
|
|
266
|
+
// check for edge-cases which don"t share common prefix
|
|
267
|
+
if (border[direction][type].indexOf(lastCh) !== -1 && parent !== "") {
|
|
268
|
+
parent = this.adjacent(parent, direction);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// append letter for direction to parent
|
|
272
|
+
return parent + Geohash.base32.charAt(neighbour[direction][type].indexOf(lastCh));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Returns all 8 adjacent cells to specified geohash.
|
|
277
|
+
*
|
|
278
|
+
* @param {string} geohash - Geohash neighbours are required of.
|
|
279
|
+
* @returns {{n,ne,e,se,s,sw,w,nw: string}}
|
|
280
|
+
* @throws Invalid geohash.
|
|
281
|
+
*/
|
|
282
|
+
neighbours(geohash) {
|
|
283
|
+
return {
|
|
284
|
+
n: this.adjacent(geohash, "n"),
|
|
285
|
+
ne: this.adjacent(this.adjacent(geohash, "n"), "e"),
|
|
286
|
+
e: this.adjacent(geohash, "e"),
|
|
287
|
+
se: this.adjacent(this.adjacent(geohash, "s"), "e"),
|
|
288
|
+
s: this.adjacent(geohash, "s"),
|
|
289
|
+
sw: this.adjacent(this.adjacent(geohash, "s"), "w"),
|
|
290
|
+
w: this.adjacent(geohash, "w"),
|
|
291
|
+
nw: this.adjacent(this.adjacent(geohash, "n"), "w")
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// HPCC Extensions ---
|
|
296
|
+
contained(w, n, e, s, precision) {
|
|
297
|
+
if (isNaN(n) || n >= 90) n = 89;
|
|
298
|
+
if (isNaN(e) || e > 180) e = 180;
|
|
299
|
+
if (isNaN(s) || s <= -90) s = -89;
|
|
300
|
+
if (isNaN(w) || w < -180) w = -180;
|
|
301
|
+
precision = precision || 1;
|
|
302
|
+
const geoHashNW = this.encode(n, w, precision);
|
|
303
|
+
const geoHashNE = this.encode(n, e, precision);
|
|
304
|
+
const geoHashSE = this.encode(s, e, precision);
|
|
305
|
+
let currRowHash = geoHashNW;
|
|
306
|
+
let col = 0;
|
|
307
|
+
let maxCol = -1;
|
|
308
|
+
const geoHashes = [geoHashNW, geoHashSE];
|
|
309
|
+
let currHash = this.adjacent(geoHashNW, "e");
|
|
310
|
+
while (currHash !== geoHashSE) {
|
|
311
|
+
geoHashes.push(currHash);
|
|
312
|
+
++col;
|
|
313
|
+
if (currHash === geoHashNE || maxCol === col) {
|
|
314
|
+
maxCol = col + 1;
|
|
315
|
+
col = 0;
|
|
316
|
+
currHash = this.adjacent(currRowHash, "s");
|
|
317
|
+
currRowHash = currHash;
|
|
318
|
+
} else {
|
|
319
|
+
currHash = this.adjacent(currHash, "e");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return geoHashes;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
calculateWidthDegrees(n) {
|
|
326
|
+
let a;
|
|
327
|
+
if (n % 2 === 0) {
|
|
328
|
+
a = -1;
|
|
329
|
+
} else {
|
|
330
|
+
a = -0.5;
|
|
331
|
+
}
|
|
332
|
+
const result = 180 / Math.pow(2, 2.5 * n + a);
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
width(n) {
|
|
337
|
+
const parity = n % 2;
|
|
338
|
+
return 180 / (2 ^ (((5 * n + parity) / 2) - 1));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function multiplex(streams) {
|
|
344
|
+
const n = streams.length;
|
|
345
|
+
return {
|
|
346
|
+
point(x, y) { let i = -1; while (++i < n) streams[i].point(x, y); },
|
|
347
|
+
sphere() { let i = -1; while (++i < n) streams[i].sphere(); },
|
|
348
|
+
lineStart() { let i = -1; while (++i < n) streams[i].lineStart(); },
|
|
349
|
+
lineEnd() { let i = -1; while (++i < n) streams[i].lineEnd(); },
|
|
350
|
+
polygonStart() { let i = -1; while (++i < n) streams[i].polygonStart(); },
|
|
351
|
+
polygonEnd() { let i = -1; while (++i < n) streams[i].polygonEnd(); }
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// A modified d3.geo.albersUsa to include Puerto Rico.
|
|
356
|
+
export function albersUsaPr() {
|
|
357
|
+
let cache;
|
|
358
|
+
let cacheStream;
|
|
359
|
+
|
|
360
|
+
const ε = 1e-6;
|
|
361
|
+
|
|
362
|
+
const lower48 = geoAlbers();
|
|
363
|
+
let lower48Point;
|
|
364
|
+
|
|
365
|
+
// EPSG:3338
|
|
366
|
+
const alaska = geoConicEqualArea()
|
|
367
|
+
.rotate([154, 0])
|
|
368
|
+
.center([-2, 58.5])
|
|
369
|
+
.parallels([55, 65]);
|
|
370
|
+
let alaskaPoint;
|
|
371
|
+
|
|
372
|
+
// ESRI:102007
|
|
373
|
+
const hawaii = geoConicEqualArea()
|
|
374
|
+
.rotate([157, 0])
|
|
375
|
+
.center([-3, 19.9])
|
|
376
|
+
.parallels([8, 18]);
|
|
377
|
+
let hawaiiPoint;
|
|
378
|
+
|
|
379
|
+
// XXX? You should check that this is a standard PR projection!
|
|
380
|
+
const puertoRico = geoConicEqualArea()
|
|
381
|
+
.rotate([66, 0])
|
|
382
|
+
.center([0, 18])
|
|
383
|
+
.parallels([8, 18]);
|
|
384
|
+
let puertoRicoPoint;
|
|
385
|
+
|
|
386
|
+
let point;
|
|
387
|
+
const pointStream = { point: (x, y) => { point = [x, y]; } } as GeoStream;
|
|
388
|
+
|
|
389
|
+
const albersUsa: any = function (coordinates) {
|
|
390
|
+
const x = coordinates[0];
|
|
391
|
+
const y = coordinates[1];
|
|
392
|
+
point = null;
|
|
393
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
394
|
+
(lower48Point.point(x, y), point) ||
|
|
395
|
+
(alaskaPoint.point(x, y), point) ||
|
|
396
|
+
(hawaiiPoint.point(x, y), point) ||
|
|
397
|
+
(puertoRicoPoint.point(x, y), point);
|
|
398
|
+
return point;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
albersUsa.invert = function (coordinates) {
|
|
402
|
+
const k = lower48.scale();
|
|
403
|
+
const t = lower48.translate();
|
|
404
|
+
const x = (coordinates[0] - t[0]) / k;
|
|
405
|
+
const y = (coordinates[1] - t[1]) / k;
|
|
406
|
+
return (y >= 0.120 && y < 0.234 && x >= -0.425 && x < -0.214 ? alaska
|
|
407
|
+
: y >= 0.166 && y < 0.234 && x >= -0.214 && x < -0.115 ? hawaii
|
|
408
|
+
: y >= 0.204 && y < 0.234 && x >= 0.320 && x < 0.380 ? puertoRico
|
|
409
|
+
: lower48).invert(coordinates);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// A naïve multi-projection stream.
|
|
413
|
+
// The projections must have mutually exclusive clip regions on the sphere,
|
|
414
|
+
// as this will avoid emitting interleaving lines and polygons.
|
|
415
|
+
albersUsa.stream = function (stream) {
|
|
416
|
+
return cache && cacheStream === stream ? cache : cache = multiplex([
|
|
417
|
+
lower48.stream(cacheStream = stream),
|
|
418
|
+
alaska.stream(stream),
|
|
419
|
+
hawaii.stream(stream),
|
|
420
|
+
puertoRico.stream(stream)
|
|
421
|
+
]);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
albersUsa.precision = function (_) {
|
|
425
|
+
if (!arguments.length) return lower48.precision();
|
|
426
|
+
lower48.precision(_);
|
|
427
|
+
alaska.precision(_);
|
|
428
|
+
hawaii.precision(_);
|
|
429
|
+
puertoRico.precision(_);
|
|
430
|
+
return reset();
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
albersUsa.scale = function (_) {
|
|
434
|
+
if (!arguments.length) return lower48.scale();
|
|
435
|
+
lower48.scale(_);
|
|
436
|
+
alaska.scale(_ * 0.35);
|
|
437
|
+
hawaii.scale(_);
|
|
438
|
+
puertoRico.scale(_);
|
|
439
|
+
return albersUsa.translate(lower48.translate());
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
albersUsa.translate = function (_) {
|
|
443
|
+
if (!arguments.length) return lower48.translate();
|
|
444
|
+
const k = lower48.scale();
|
|
445
|
+
const x = +_[0];
|
|
446
|
+
const y = +_[1];
|
|
447
|
+
|
|
448
|
+
lower48Point = lower48
|
|
449
|
+
.translate(_)
|
|
450
|
+
.clipExtent([[x - 0.455 * k, y - 0.238 * k], [x + 0.455 * k, y + 0.238 * k]])
|
|
451
|
+
.stream(pointStream);
|
|
452
|
+
|
|
453
|
+
alaskaPoint = alaska
|
|
454
|
+
.translate([x - 0.307 * k, y + 0.201 * k])
|
|
455
|
+
.clipExtent([[x - 0.425 * k + ε, y + 0.120 * k + ε], [x - 0.214 * k - ε, y + 0.234 * k - ε]])
|
|
456
|
+
.stream(pointStream);
|
|
457
|
+
|
|
458
|
+
hawaiiPoint = hawaii
|
|
459
|
+
.translate([x - 0.205 * k, y + 0.212 * k])
|
|
460
|
+
.clipExtent([[x - 0.214 * k + ε, y + 0.166 * k + ε], [x - 0.115 * k - ε, y + 0.234 * k - ε]])
|
|
461
|
+
.stream(pointStream);
|
|
462
|
+
|
|
463
|
+
puertoRicoPoint = puertoRico
|
|
464
|
+
.translate([x + 0.350 * k, y + 0.224 * k])
|
|
465
|
+
.clipExtent([[x + 0.320 * k, y + 0.204 * k], [x + 0.380 * k, y + 0.234 * k]])
|
|
466
|
+
.stream(pointStream);
|
|
467
|
+
|
|
468
|
+
return reset();
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
albersUsa.fitExtent = function (extent, object) {
|
|
472
|
+
return fitExtent(albersUsa, extent, object);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
albersUsa.fitSize = function (size, object) {
|
|
476
|
+
return fitSize(albersUsa, size, object);
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
function reset() {
|
|
480
|
+
cache = cacheStream = null;
|
|
481
|
+
return albersUsa;
|
|
482
|
+
}
|
|
483
|
+
return albersUsa.scale(1070);
|
|
484
|
+
}
|