@verifyhash/koppen-classifier 0.1.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/LICENSE +21 -0
- package/README.md +152 -0
- package/index.js +167 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 <OWNER-NAME PLACEHOLDER>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# koppen-classifier
|
|
2
|
+
|
|
3
|
+
<!-- publish-prep -->
|
|
4
|
+
> **TODO (owner): pick the final npm name/scope before publishing.**
|
|
5
|
+
> The npm package name is **not** finalized by this project. Convention used here:
|
|
6
|
+
> `package.json` keeps the working slug `koppen-classifier` as a placeholder — replace it
|
|
7
|
+
> (and the `npm install` line below) with the real published name/scope before
|
|
8
|
+
> running `npm publish`.
|
|
9
|
+
|
|
10
|
+
A tiny, **zero-dependency** Node library that assigns a location its
|
|
11
|
+
**Köppen–Geiger climate class** (e.g. `Cfa`, `BWh`, `Dfb`) from twelve monthly
|
|
12
|
+
mean temperatures and twelve monthly precipitation totals. Pure function, no
|
|
13
|
+
network, no files, no state — give it numbers, get back a code.
|
|
14
|
+
|
|
15
|
+
The classification logic is a direct port of the **proven** `koppenClass()`
|
|
16
|
+
that has run in production on [weatherhack.com](https://weatherhack.com) to
|
|
17
|
+
label real cities from ERA5 normals. It was lifted, not re-derived, so its
|
|
18
|
+
boundaries match what that site already ships.
|
|
19
|
+
|
|
20
|
+
## Who it's for
|
|
21
|
+
|
|
22
|
+
Anyone who has monthly climate normals and wants the Köppen letter code without
|
|
23
|
+
pulling in a heavy geodata package: map/dashboard builders, teaching material,
|
|
24
|
+
data-pipeline enrichment, quiz/trivia generators, worldbuilding tools.
|
|
25
|
+
|
|
26
|
+
## Install / use
|
|
27
|
+
|
|
28
|
+
No install step for local use — it's one file with no dependencies. Copy the
|
|
29
|
+
folder in, or `require('./koppen-classifier')`.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
const { classify } = require('koppen-classifier');
|
|
33
|
+
|
|
34
|
+
// London, UK — monthly arrays run January → December.
|
|
35
|
+
const london = classify({
|
|
36
|
+
tempsC: [5.2, 5.3, 7.6, 9.6, 12.9, 16.0, 18.1, 17.8, 15.2, 11.4, 7.8, 5.5],
|
|
37
|
+
precipMm: [55, 41, 42, 44, 49, 45, 45, 50, 49, 69, 59, 55],
|
|
38
|
+
lat: 51.5,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log(london.code); // 'Cfb'
|
|
42
|
+
console.log(london.label); // 'temperate oceanic'
|
|
43
|
+
console.log(london.groupLabel); // 'Temperate'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
### `classify(options) → result`
|
|
49
|
+
|
|
50
|
+
**`options`**
|
|
51
|
+
|
|
52
|
+
| field | type | required | notes |
|
|
53
|
+
|--------------|------------|----------|-------|
|
|
54
|
+
| `tempsC` | `number[]` | yes | 12 monthly **mean** temperatures in °C, **Jan → Dec**. |
|
|
55
|
+
| `precipMm` | `number[]` | yes | 12 monthly precipitation totals in mm, **Jan → Dec**. |
|
|
56
|
+
| `lat` | `number` | one of these | Signed latitude, north positive. Only its **sign** is used, to pick the warm half-year. |
|
|
57
|
+
| `hemisphere` | `string` | one of these | `'N'` / `'S'` (also `'north'` / `'south'`). Alternative to `lat`. |
|
|
58
|
+
|
|
59
|
+
Provide **either** `lat` **or** `hemisphere`. If both are present, `lat` wins.
|
|
60
|
+
Invalid input (arrays not length 12, non-finite numbers, no hemisphere given)
|
|
61
|
+
throws a `TypeError` rather than returning a wrong answer.
|
|
62
|
+
|
|
63
|
+
> Note: pass the monthly **mean** temperature. If your source has separate daily
|
|
64
|
+
> highs and lows, average them first: `mean = (high + low) / 2`. That is exactly
|
|
65
|
+
> what weatherhack does before calling the original function.
|
|
66
|
+
|
|
67
|
+
**`result`** (a plain object)
|
|
68
|
+
|
|
69
|
+
| field | type | example | meaning |
|
|
70
|
+
|--------------|----------|------------------------|---------|
|
|
71
|
+
| `code` | `string` | `'Cfb'` | Köppen–Geiger class code. |
|
|
72
|
+
| `label` | `string` | `'temperate oceanic'` | Human-readable class name. |
|
|
73
|
+
| `group` | `string` | `'C'` | Top-level group letter. |
|
|
74
|
+
| `groupLabel` | `string` | `'Temperate'` | Top-level group name (Tropical / Arid / Temperate / Continental / Polar). |
|
|
75
|
+
| `blurb` | `string` | `'A coldest month …'` | One-sentence plain-language summary. |
|
|
76
|
+
|
|
77
|
+
## What the algorithm actually does
|
|
78
|
+
|
|
79
|
+
It applies the standard Köppen decision tree, tested in this order:
|
|
80
|
+
|
|
81
|
+
1. **B (arid)** first, because aridity overrides temperature. The dryness
|
|
82
|
+
threshold is `P_th = 20·MAT + offset`, where the offset is **280** if ≥70% of
|
|
83
|
+
annual precipitation falls in the warm half-year, **140** if 30–70%, else
|
|
84
|
+
**0**. `BW` (desert) when annual precip `< ½·P_th`, otherwise `BS` (steppe);
|
|
85
|
+
the `h`/`k` split is at a mean annual temperature of **18 °C**.
|
|
86
|
+
2. **A (tropical)** when the coldest month averages ≥ 18 °C (`Af`/`Am`/`Aw`/`As`).
|
|
87
|
+
3. **E (polar)** when the warmest month is below 10 °C (`ET` tundra, `EF` ice cap).
|
|
88
|
+
4. **C vs D** by the **0 °C coldest-month isotherm**, then a seasonal-precip
|
|
89
|
+
third letter (`f`/`s`/`w`) and a summer-heat fourth letter (`a`/`b`/`c`/`d`).
|
|
90
|
+
|
|
91
|
+
The **warm half-year** is April–September in the northern hemisphere and
|
|
92
|
+
October–March in the southern — this is why the hemisphere argument matters, and
|
|
93
|
+
why a southern-hemisphere Mediterranean city (dry *December–February* summer)
|
|
94
|
+
comes out `Cs…` rather than `Cw…`.
|
|
95
|
+
|
|
96
|
+
### Honest limits
|
|
97
|
+
|
|
98
|
+
- This is the common textbook variant of the boundaries. Other published
|
|
99
|
+
variants differ on edge rules (e.g. the exact aridity offset, or a 22 °C vs
|
|
100
|
+
0 °C `h`/`k` split, or the As/Aw threshold). Results near a boundary can
|
|
101
|
+
legitimately differ from other tools; this one matches weatherhack's output.
|
|
102
|
+
- It classifies from the **12 monthly normals you supply**. Garbage or
|
|
103
|
+
non-representative normals in → wrong class out; it has no way to know your
|
|
104
|
+
inputs are unusual.
|
|
105
|
+
- Southern/northern is derived purely from the sign of `lat` (or the
|
|
106
|
+
`hemisphere` string). Equatorial locations (`lat` ≈ 0) are treated as northern;
|
|
107
|
+
for tropical `A` climates the hemisphere choice rarely changes the code.
|
|
108
|
+
- The four-letter `d` sub-code and some rare combinations are emitted but not
|
|
109
|
+
every one is covered by a named-city fixture in the test suite.
|
|
110
|
+
|
|
111
|
+
## Tests
|
|
112
|
+
|
|
113
|
+
One command, no framework, no dependencies:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
node test/koppen.test.js
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
It checks known-city fixtures across every top-level group — London `Cfb`,
|
|
120
|
+
Singapore `Af`, Cairo `BWh`, Moscow `Dfb`, Cape Town `Csb` (southern
|
|
121
|
+
hemisphere), Utqiagvik `ET` (polar) — plus a hemisphere-flip case proving the
|
|
122
|
+
`lat`/`hemisphere` argument changes the result, the return-shape contract, and
|
|
123
|
+
input validation. Exit code is non-zero on any failure.
|
|
124
|
+
|
|
125
|
+
## Status
|
|
126
|
+
|
|
127
|
+
Incubator project — an npm-graduation **candidate**, not published. Promotion to
|
|
128
|
+
a real package registry is a separate, human-approved step.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT.
|
|
133
|
+
|
|
134
|
+
## Install
|
|
135
|
+
|
|
136
|
+
> Placeholder name: the `koppen-classifier` below is the working slug, not a finalized
|
|
137
|
+
> npm name — see the owner TODO near the top of this README.
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
npm install koppen-classifier
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
const { classify } = require('koppen-classifier');
|
|
145
|
+
|
|
146
|
+
const london = classify({
|
|
147
|
+
tempsC: [5.2, 5.3, 7.6, 9.6, 12.9, 16.0, 18.1, 17.8, 15.2, 11.4, 7.8, 5.5],
|
|
148
|
+
precipMm: [55, 41, 42, 44, 49, 45, 45, 50, 49, 69, 59, 55],
|
|
149
|
+
lat: 51.5,
|
|
150
|
+
});
|
|
151
|
+
console.log(london.code); // 'Cfb'
|
|
152
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// koppen-classifier — a zero-dependency Köppen–Geiger climate classifier.
|
|
4
|
+
//
|
|
5
|
+
// Given a location's twelve monthly mean temperatures (°C) and twelve monthly
|
|
6
|
+
// precipitation totals (mm), classify() returns the Köppen–Geiger climate code
|
|
7
|
+
// (e.g. 'Cfa'), a human-readable label, and the top-level group.
|
|
8
|
+
//
|
|
9
|
+
// The decision tree is a straight port of the PROVEN koppenClass() used in
|
|
10
|
+
// production on weatherhack.com (generate.cjs). The only adaptation is the
|
|
11
|
+
// input shape: weatherhack passes daily-high/low pairs and takes the mean
|
|
12
|
+
// internally as (hi+lo)/2; this library accepts the monthly MEAN temperature
|
|
13
|
+
// directly (tempsC), which is the exact quantity the algorithm consumes. The
|
|
14
|
+
// classification boundaries below are byte-for-byte identical to the source.
|
|
15
|
+
|
|
16
|
+
// Human-readable names for the Köppen–Geiger codes we can emit. Codes not in
|
|
17
|
+
// the table fall back to the raw code, so the classifier can never crash on an
|
|
18
|
+
// unusual combination.
|
|
19
|
+
const KOPPEN_NAMES = {
|
|
20
|
+
Af: 'tropical rainforest', Am: 'tropical monsoon',
|
|
21
|
+
Aw: 'tropical savanna (dry winter)', As: 'tropical savanna (dry summer)',
|
|
22
|
+
BWh: 'hot desert', BWk: 'cold desert',
|
|
23
|
+
BSh: 'hot semi-arid (steppe)', BSk: 'cold semi-arid (steppe)',
|
|
24
|
+
Csa: 'hot-summer Mediterranean', Csb: 'warm-summer Mediterranean', Csc: 'cold-summer Mediterranean',
|
|
25
|
+
Cwa: 'monsoon-influenced humid subtropical', Cwb: 'subtropical highland', Cwc: 'cold subtropical highland',
|
|
26
|
+
Cfa: 'humid subtropical', Cfb: 'temperate oceanic', Cfc: 'subpolar oceanic',
|
|
27
|
+
Dsa: 'Mediterranean-influenced hot-summer continental', Dsb: 'Mediterranean-influenced warm-summer continental',
|
|
28
|
+
Dsc: 'Mediterranean-influenced subarctic', Dsd: 'Mediterranean-influenced extremely cold subarctic',
|
|
29
|
+
Dwa: 'monsoon-influenced hot-summer continental', Dwb: 'monsoon-influenced warm-summer continental',
|
|
30
|
+
Dwc: 'monsoon-influenced subarctic', Dwd: 'monsoon-influenced extremely cold subarctic',
|
|
31
|
+
Dfa: 'hot-summer humid continental', Dfb: 'warm-summer humid continental',
|
|
32
|
+
Dfc: 'subarctic', Dfd: 'extremely cold subarctic',
|
|
33
|
+
ET: 'polar tundra', EF: 'polar ice cap',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Top-level group (first letter of the code) → short name.
|
|
37
|
+
const GROUP_NAMES = {
|
|
38
|
+
A: 'Tropical',
|
|
39
|
+
B: 'Arid',
|
|
40
|
+
C: 'Temperate',
|
|
41
|
+
D: 'Continental',
|
|
42
|
+
E: 'Polar',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function fmt1(n) {
|
|
46
|
+
return (Math.round(n * 10) / 10).toFixed(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Resolve the hemisphere from the options. Accepts either `lat` (a signed
|
|
50
|
+
// latitude in degrees, north positive) or `hemisphere` ('N'/'S', 'north'/
|
|
51
|
+
// 'south', case-insensitive). `lat` wins if both are given. Returns true for
|
|
52
|
+
// the northern hemisphere.
|
|
53
|
+
function isNorth(opts) {
|
|
54
|
+
if (typeof opts.lat === 'number' && Number.isFinite(opts.lat)) {
|
|
55
|
+
return opts.lat >= 0;
|
|
56
|
+
}
|
|
57
|
+
if (opts.hemisphere != null) {
|
|
58
|
+
const h = String(opts.hemisphere).trim().toLowerCase();
|
|
59
|
+
if (h === 'n' || h === 'north' || h === 'northern') return true;
|
|
60
|
+
if (h === 's' || h === 'south' || h === 'southern') return false;
|
|
61
|
+
throw new TypeError(
|
|
62
|
+
`classify: unrecognised hemisphere ${JSON.stringify(opts.hemisphere)} (use 'N'/'S' or a numeric lat)`);
|
|
63
|
+
}
|
|
64
|
+
throw new TypeError("classify: provide either `lat` (number) or `hemisphere` ('N'/'S')");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function assert12(name, arr) {
|
|
68
|
+
if (!Array.isArray(arr) || arr.length !== 12) {
|
|
69
|
+
throw new TypeError(`classify: \`${name}\` must be an array of exactly 12 numbers`);
|
|
70
|
+
}
|
|
71
|
+
for (let i = 0; i < 12; i++) {
|
|
72
|
+
if (typeof arr[i] !== 'number' || !Number.isFinite(arr[i])) {
|
|
73
|
+
throw new TypeError(`classify: \`${name}[${i}]\` must be a finite number`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Classify a location's Köppen–Geiger climate.
|
|
80
|
+
*
|
|
81
|
+
* @param {object} opts
|
|
82
|
+
* @param {number[]} opts.tempsC 12 monthly MEAN temperatures in °C, Jan → Dec.
|
|
83
|
+
* @param {number[]} opts.precipMm 12 monthly precipitation totals in mm, Jan → Dec.
|
|
84
|
+
* @param {number} [opts.lat] Signed latitude (north positive). Used only for
|
|
85
|
+
* its sign, to pick the warm half-year.
|
|
86
|
+
* @param {string} [opts.hemisphere] 'N'/'S' (or 'north'/'south') — alternative to `lat`.
|
|
87
|
+
* @returns {{code:string, label:string, group:string, groupLabel:string, blurb:string}}
|
|
88
|
+
* code e.g. 'Cfa'
|
|
89
|
+
* label human-readable class name, e.g. 'humid subtropical'
|
|
90
|
+
* group top-level letter, e.g. 'C'
|
|
91
|
+
* groupLabel top-level name, e.g. 'Temperate'
|
|
92
|
+
* blurb one-sentence plain-language summary
|
|
93
|
+
*/
|
|
94
|
+
function classify(opts) {
|
|
95
|
+
if (!opts || typeof opts !== 'object') {
|
|
96
|
+
throw new TypeError('classify: expected an options object { tempsC, precipMm, lat|hemisphere }');
|
|
97
|
+
}
|
|
98
|
+
assert12('tempsC', opts.tempsC);
|
|
99
|
+
assert12('precipMm', opts.precipMm);
|
|
100
|
+
const north = isNorth(opts);
|
|
101
|
+
|
|
102
|
+
// T is the monthly mean temperature directly (weatherhack derives it as
|
|
103
|
+
// (hi+lo)/2 before this exact point). P is monthly precipitation in mm.
|
|
104
|
+
const T = opts.tempsC.slice();
|
|
105
|
+
const P = opts.precipMm.slice();
|
|
106
|
+
|
|
107
|
+
const MAT = T.reduce((a, b) => a + b, 0) / 12;
|
|
108
|
+
const Pann = P.reduce((a, b) => a + b, 0);
|
|
109
|
+
const Tcold = Math.min(...T);
|
|
110
|
+
const Twarm = Math.max(...T);
|
|
111
|
+
const nWarm10 = T.filter((t) => t >= 10).length;
|
|
112
|
+
|
|
113
|
+
const summerIdx = north ? [3, 4, 5, 6, 7, 8] : [9, 10, 11, 0, 1, 2];
|
|
114
|
+
const winterIdx = north ? [9, 10, 11, 0, 1, 2] : [3, 4, 5, 6, 7, 8];
|
|
115
|
+
const Psummer = summerIdx.reduce((a, i) => a + P[i], 0);
|
|
116
|
+
const summerShare = Pann > 0 ? Psummer / Pann : 0;
|
|
117
|
+
const Psmin = Math.min(...summerIdx.map((i) => P[i]));
|
|
118
|
+
const Psmax = Math.max(...summerIdx.map((i) => P[i]));
|
|
119
|
+
const Pwmin = Math.min(...winterIdx.map((i) => P[i]));
|
|
120
|
+
const Pwmax = Math.max(...winterIdx.map((i) => P[i]));
|
|
121
|
+
const Pmin = Math.min(...P);
|
|
122
|
+
|
|
123
|
+
const done = (code) => ({
|
|
124
|
+
code,
|
|
125
|
+
label: KOPPEN_NAMES[code] || code,
|
|
126
|
+
group: code[0],
|
|
127
|
+
groupLabel: GROUP_NAMES[code[0]] || code[0],
|
|
128
|
+
blurb: `A coldest month averaging ${fmt1(Tcold)} °C, a warmest month near ${fmt1(Twarm)} °C `
|
|
129
|
+
+ `and about ${Math.round(Pann)} mm of precipitation across the year place it in the `
|
|
130
|
+
+ `${KOPPEN_NAMES[code] || code} band.`,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// B (arid) is tested first — it overrides the temperature classes.
|
|
134
|
+
const offset = summerShare >= 0.7 ? 280 : summerShare >= 0.3 ? 140 : 0;
|
|
135
|
+
const Pth = 20 * MAT + offset;
|
|
136
|
+
if (Pann < Pth) {
|
|
137
|
+
return done('B' + (Pann < 0.5 * Pth ? 'W' : 'S') + (MAT >= 18 ? 'h' : 'k'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Fourth letter (summer heat) for C/D climates.
|
|
141
|
+
const heat = () =>
|
|
142
|
+
Twarm >= 22 ? 'a' : nWarm10 >= 4 ? 'b' : Tcold > -38 ? 'c' : 'd';
|
|
143
|
+
// Third letter (seasonal precipitation) for C/D climates.
|
|
144
|
+
const precip = () => {
|
|
145
|
+
const dryS = Psmin < 40 && Psmin < Pwmax / 3; // dry summer
|
|
146
|
+
const dryW = Pwmin < Psmax / 10; // dry winter
|
|
147
|
+
if (dryS && !dryW) return 's';
|
|
148
|
+
if (dryW && !dryS) return 'w';
|
|
149
|
+
if (dryS && dryW) return Psmin <= Pwmin ? 's' : 'w';
|
|
150
|
+
return 'f';
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// A (tropical): every month's mean ≥ 18 °C (coldest month ≥ 18).
|
|
154
|
+
if (Tcold >= 18) {
|
|
155
|
+
let third;
|
|
156
|
+
if (Pmin >= 60) third = 'f';
|
|
157
|
+
else if (Pmin >= 100 - Pann / 25) third = 'm';
|
|
158
|
+
else third = summerShare >= 0.5 ? 'w' : 's'; // dry season in winter → Aw
|
|
159
|
+
return done('A' + third);
|
|
160
|
+
}
|
|
161
|
+
// E (polar): warmest month below 10 °C.
|
|
162
|
+
if (Twarm < 10) return done(Twarm >= 0 ? 'ET' : 'EF');
|
|
163
|
+
// C / D by the 0 °C coldest-month isotherm.
|
|
164
|
+
return done((Tcold > 0 ? 'C' : 'D') + precip() + heat());
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = { classify, KOPPEN_NAMES, GROUP_NAMES };
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@verifyhash/koppen-classifier",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency Köppen–Geiger climate classifier from 12 monthly mean-temperature and precipitation values (hemisphere-aware).",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node test/koppen.test.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"koppen",
|
|
12
|
+
"koppen-geiger",
|
|
13
|
+
"climate",
|
|
14
|
+
"classification",
|
|
15
|
+
"weather",
|
|
16
|
+
"geography"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"files": [
|
|
20
|
+
"index.js",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/verifyhash/libs.git",
|
|
29
|
+
"directory": "koppen-classifier"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/verifyhash/libs/tree/main/koppen-classifier#readme",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/verifyhash/libs/issues"
|
|
34
|
+
}
|
|
35
|
+
}
|