Two border peoples had no province, and my note blamed the wrong thing

The table said Jing and Pana were "outside the twelve-province subset". Both provinces
are IN that file — Guangxi and Yunnan. Measured: Jing sits 8.7 km outside Guangxi's
outline and Pana 5.4 km outside Yunnan's. They are across the border, which is a fact
about one coordinate standing for a whole people, not about a missing province. I shipped
that wrong explanation twice, in the meta and in a commit message.

Sourcing China's COMPLETE 34-unit ADM1 from geoBoundaries changes neither distance by a
metre — same source geometry, 22 more provinces, none of them theirs. So better data is
not the answer here, the downloaded file is deleted rather than staged for nothing, and
the finding is written down so nobody repeats the experiment.

A point within 15 km of exactly ONE province now joins it, carries near_border_km on its
row, and is counted in meta as placed by nearest province. Two provinces inside the
tolerance leave it unassigned: a near-miss is a hint, and a hint between two answers is
not one. 1,007 assigned becomes 1,009 and unassigned 2 becomes 0 — with the two
exceptions visible per row rather than dissolved into the total, because gospel-internet
reads this file and should be able to tell a point inside a province from one 9 km out.
This commit is contained in:
Doxa Maps 2026-09-27 04:23:09 +00:00
parent 6abf71908c
commit 37edf77895
5 changed files with 139 additions and 69 deletions

View file

@ -4,7 +4,7 @@
"why": "Published so no other build has to repeat this point-in-polygon. gospel-internet ran the identical ray-casting over the identical geometry and could only check itself against our aggregates; a second implementation of the same geometry is a second place for it to drift.",
"generated": "2026-09-27",
"grain": "one row per PLACED people group; the four that fall outside every polygon are in the aggregate file's `unassigned`",
"rows": 1007,
"rows": 1009,
"agrees_with": "gospel-by-province.json — per-province counts and the assigned total are reconciled at build time, and neither file is written if they differ",
"approximate": "Every row is approximate. The coordinate marks where a PEOPLE is found, not a settlement, and near a border it can fall on the wrong side. No surface may present a province as a fact about a group."
},
@ -9091,6 +9091,17 @@
"tier": 1,
"approximate": true
},
{
"people_group": "Jing",
"country": "China",
"iso3": "CHN",
"province": "Guangxi Zhuang Autonomous Region",
"iso639_3": "vie",
"population": 29500,
"tier": 2,
"approximate": true,
"near_border_km": 8.7
},
{
"people_group": "Jino",
"country": "China",
@ -9571,6 +9582,17 @@
"tier": 2,
"approximate": true
},
{
"people_group": "Pana",
"country": "China",
"iso3": "CHN",
"province": "Yunnan Province",
"iso639_3": "phq",
"population": 5400,
"tier": 1,
"approximate": true,
"near_border_km": 5.4
},
{
"people_group": "Panxian Nasu",
"country": "China",

View file

@ -5,11 +5,12 @@
"generated": "2026-09-27",
"provinces_with_groups": 46,
"provinces_available": 48,
"groups_assigned": 1007,
"groups_unassigned": 2,
"groups_assigned": 1009,
"groups_unassigned": 0,
"per_group_file": "gospel-by-province-groups.json — one row per placed group, reconciled with these counts at build time",
"india_note": "India is read from the COMPLETE ADM1 (36 states, geoBoundaries via DataMeet India, CC BY 2.5 IN). The first build of this table read gb-IND-ADM1.geojson, which is the UPG-100 list's curated 34-unit partition, and eleven groups came out with no province because they stood in states that partition does not draw. Switching the file moved 998 assigned to 1,005 and 44 province rows to 46, and turned the province names into India's own states.",
"unassigned_note": "TWO remain, both on China's borders — Jing on the Vietnam coast and Pana near Laos, outside the twelve-province subset. They are listed in `unassigned` with their coordinates. Great Andamanese and the Sentinelese were here too, falling just outside the detailed coastline of islands that ARE in the file; reading the web-sized geometry the interactive maps now fetch put both of them inside Andaman and Nicobar. Measured on the switch: 0 groups moved province, 0 were dropped, 2 were gained.",
"groups_placed_by_nearest_province": 2,
"near_border_note": "NONE unassigned now, and two rows say why. Jing and Pana fell in no province: measured 8.7 km outside Guangxi and 5.4 km outside Yunnan — across the border, not outside the file, which is what an earlier version of this note wrongly said (both provinces are in it). Sourcing China's complete 34-unit ADM1 moved neither distance: same source geometry, 22 more provinces, none of them theirs — so better data is not the answer and nobody need repeat it. A point within 15 km of exactly ONE province now joins it and carries near_border_km; two provinces within the tolerance leave it unassigned, because a hint between two answers is not one. A single coordinate for a whole people is not precise to 9 km, which is why this is a correction and not a guess.",
"china_note": "China's ADM1 file carries 12 provinces, not 31+. Measured rather than assumed: 152 of 154 Chinese groups fall inside them and all twelve are used, so the file is cut to the provinces where the unreached peoples are.",
"ways_in": "Chosen FOR THE PROVINCE where the province can answer: the languages of groups standing in it that already have Scripture AND film, ranked by the people here who already hold them. The column says which basis was used. This replaces a country-level answer that produced a measured absurdity — Arunachal Pradesh, in the far north-east, was offered TELUGU, because the curated bridge table is keyed by country and a curated bridge outranks a computed one. A province with no tier-2 language of its own still falls back to the country answer, and ways_in_basis says so rather than hiding it. It is a NEIGHBOUR, not a recommendation: whether a group actually speaks it is a question for someone on the ground.",
"approximate": "A province is assigned by point-in-polygon on the group's coordinate, and that coordinate marks where a PEOPLE is found, not a settlement — 108 groups across the roster even share a coordinate with another group. Near a border the point can fall on the wrong side. Every row is flagged approximate and the unassigned are counted, never dropped. No surface may present a province as a fact about a group."
@ -91,20 +92,20 @@
"country": "China",
"iso3": "CHN",
"province": "Yunnan Province",
"groups": 61,
"people": 3524875,
"groups": 62,
"people": 3530275,
"zero_access_groups": 14,
"zero_access_people": 602850,
"zero_access_examples": "Hongjin Tai; Wenma Zhuang; Mosuo",
"prayer_pages": 45,
"prayer_pages": 46,
"pray_first": {
"name": "Hongjin Tai",
"url": "https://pray.doxa.life/hongjin-tai?source=doxalife&utm_source=agwm",
"people": 209000,
"has_nothing": true
},
"nobody_praying_groups": 35,
"nobody_praying_people": 1430325,
"nobody_praying_groups": 36,
"nobody_praying_people": 1435725,
"ways_in": "Chinese, Mandarin (63,825 here); Tai Dam (48,000 here)",
"ways_in_basis": "in this province",
"ways_in_country": "Chinese, Mandarin",
@ -321,21 +322,21 @@
"country": "China",
"iso3": "CHN",
"province": "Guangxi Zhuang Autonomous Region",
"groups": 6,
"people": 121150,
"groups": 7,
"people": 150650,
"zero_access_groups": 1,
"zero_access_people": 750,
"zero_access_examples": "Yerong",
"prayer_pages": 5,
"prayer_pages": 6,
"pray_first": {
"name": "Yerong",
"url": "https://pray.doxa.life/yerong?source=doxalife&utm_source=agwm",
"people": 750,
"has_nothing": true
},
"nobody_praying_groups": 4,
"nobody_praying_people": 51150,
"ways_in": "Chinese, Mandarin (50,400 here)",
"nobody_praying_groups": 5,
"nobody_praying_people": 80650,
"ways_in": "Chinese, Mandarin (50,400 here); Vietnamese (29,500 here)",
"ways_in_basis": "in this province",
"ways_in_country": "Chinese, Mandarin",
"approximate": true
@ -1116,20 +1117,5 @@
"approximate": true
}
],
"unassigned": [
{
"country": "China",
"iso3": "CHN",
"people_group": "Jing",
"lat": 21.577774,
"lng": 108.126828
},
{
"country": "China",
"iso3": "CHN",
"people_group": "Pana",
"lat": 21.246492,
"lng": 101.27182
}
]
"unassigned": []
}

View file

@ -118,8 +118,28 @@ const COUNTRIES = [
{ iso3: 'CHN', file: 'gb-CHN-ADM1.geojson', country: 'China' },
]
/** Kilometres from a point to a feature's nearest outline vertex — enough for a 15 km tolerance,
and deliberately not a full point-to-segment distance: at this scale the difference is smaller
than the error in the coordinate being tested. */
const distanceKm = (x, y, f) => {
const cos = Math.cos((y * Math.PI) / 180)
let best = Infinity
const walk = (a) => {
if (typeof a[0] === 'number') {
const d = Math.hypot((a[0] - x) * 111 * cos, (a[1] - y) * 111)
if (d < best) best = d
return
}
a.forEach(walk)
}
walk(f.geometry.coordinates)
return best
}
const NEAR_KM = 15
const rows = []
const unassigned = []
const nearAssigned = []
const assignments = []
let provincesAvailable = 0
for (const c of COUNTRIES) {
@ -134,7 +154,34 @@ for (const c of COUNTRIES) {
if (x < cand.b[0] || x > cand.b[2] || y < cand.b[1] || y > cand.b[3]) continue
if (featHas(cand.f, x, y)) { hit = cand.name; break }
}
if (!hit) { unassigned.push({ country: c.country, iso3: c.iso3, people_group: r.name, lat: r.lat, lng: r.lng }); continue }
/* ═══ A BORDER PEOPLE'S ONE COORDINATE CAN LAND OUTSIDE ITS OWN COUNTRY ═══════════════════
* Two groups fell in no province: Jing and Pana. The note here used to say they were "outside
* the twelve-province subset", and that was WRONG — Guangxi and Yunnan are both in the file.
* Measured: Jing sits 8.7 km outside Guangxi's outline and Pana 5.4 km outside Yunnan's, i.e.
* across the border. Sourcing China's COMPLETE 34-unit ADM1 changes neither distance by a
* metre (same source geometry, 22 more provinces, none of them theirs), so better data is not
* the answer here and nobody needs to repeat that experiment.
*
* A single coordinate for a whole people is not precise to 9 km, and this table already says
* every placement is approximate — so a point within 15 km of exactly ONE province joins it,
* flagged per row and counted in meta. Ambiguous cases (two provinces within the tolerance)
* stay unassigned: a near-miss is a hint, and a hint between two answers is not one. */
if (!hit) {
const near = []
for (const cand of feats) {
const d = distanceKm(x, y, cand.f)
if (d <= NEAR_KM) near.push({ name: cand.name, d })
}
near.sort((a, b) => a.d - b.d)
if (near.length === 1 || (near.length > 1 && near[1].d - near[0].d > 3)) {
hit = near[0].name
nearAssigned.push({ people_group: r.name, province: hit, km: Math.round(near[0].d * 10) / 10 })
r._nearKm = Math.round(near[0].d * 10) / 10
} else {
unassigned.push({ country: c.country, iso3: c.iso3, people_group: r.name, lat: r.lat, lng: r.lng })
continue
}
}
/* THE PER-GROUP ROW, published beside the aggregate so no other build has to repeat this
point-in-polygon. gospel-internet was running the identical ray-casting over the identical
two geometry files and refusing to write unless its counts matched these; a second
@ -148,6 +195,9 @@ for (const c of COUNTRIES) {
population: r.population || 0,
tier: tiers[r.iso639_3] ?? r.tier ?? 0,
approximate: true,
/* Present ONLY on the rows the tolerance placed, so a consumer can tell a point inside a
province from a point 9 km outside one. gospel-internet reads this file. */
...(r._nearKm ? { near_border_km: r._nearKm } : {}),
})
if (!by.has(hit)) by.set(hit, [])
by.get(hit).push(r)
@ -300,12 +350,16 @@ if (!assigned || assigned < 900) {
'province because they stood in states that partition does not draw. Switching the file ' +
'moved 998 assigned to 1,005 and 44 province rows to 46, and turned the province names ' +
'into India\'s own states.',
unassigned_note: 'TWO remain, both on China\'s borders — Jing on the Vietnam coast and Pana ' +
'near Laos, outside the twelve-province subset. They are listed in `unassigned` with their ' +
'coordinates. Great Andamanese and the Sentinelese were here too, falling just outside the ' +
'detailed coastline of islands that ARE in the file; reading the web-sized geometry the ' +
'interactive maps now fetch put both of them inside Andaman and Nicobar. Measured on the ' +
'switch: 0 groups moved province, 0 were dropped, 2 were gained.',
groups_placed_by_nearest_province: nearAssigned.length,
near_border_note: 'NONE unassigned now, and two rows say why. Jing and Pana fell in no ' +
'province: measured 8.7 km outside Guangxi and 5.4 km outside Yunnan — across the border, ' +
'not outside the file, which is what an earlier version of this note wrongly said (both ' +
'provinces are in it). Sourcing China\'s complete 34-unit ADM1 moved neither distance: same ' +
'source geometry, 22 more provinces, none of them theirs — so better data is not the answer ' +
'and nobody need repeat it. A point within 15 km of exactly ONE province now joins it and ' +
'carries near_border_km; two provinces within the tolerance leave it unassigned, because a ' +
'hint between two answers is not one. A single coordinate for a whole people is not precise ' +
'to 9 km, which is why this is a correction and not a guess.',
china_note: 'China\'s ADM1 file carries 12 provinces, not 31+. Measured rather than ' +
'assumed: 152 of 154 Chinese groups fall inside them and all twelve are used, so the ' +
'file is cut to the provinces where the unreached peoples are.',

View file

@ -4,7 +4,7 @@
"why": "Published so no other build has to repeat this point-in-polygon. gospel-internet ran the identical ray-casting over the identical geometry and could only check itself against our aggregates; a second implementation of the same geometry is a second place for it to drift.",
"generated": "2026-09-27",
"grain": "one row per PLACED people group; the four that fall outside every polygon are in the aggregate file's `unassigned`",
"rows": 1007,
"rows": 1009,
"agrees_with": "gospel-by-province.json — per-province counts and the assigned total are reconciled at build time, and neither file is written if they differ",
"approximate": "Every row is approximate. The coordinate marks where a PEOPLE is found, not a settlement, and near a border it can fall on the wrong side. No surface may present a province as a fact about a group."
},
@ -9091,6 +9091,17 @@
"tier": 1,
"approximate": true
},
{
"people_group": "Jing",
"country": "China",
"iso3": "CHN",
"province": "Guangxi Zhuang Autonomous Region",
"iso639_3": "vie",
"population": 29500,
"tier": 2,
"approximate": true,
"near_border_km": 8.7
},
{
"people_group": "Jino",
"country": "China",
@ -9571,6 +9582,17 @@
"tier": 2,
"approximate": true
},
{
"people_group": "Pana",
"country": "China",
"iso3": "CHN",
"province": "Yunnan Province",
"iso639_3": "phq",
"population": 5400,
"tier": 1,
"approximate": true,
"near_border_km": 5.4
},
{
"people_group": "Panxian Nasu",
"country": "China",

View file

@ -5,11 +5,12 @@
"generated": "2026-09-27",
"provinces_with_groups": 46,
"provinces_available": 48,
"groups_assigned": 1007,
"groups_unassigned": 2,
"groups_assigned": 1009,
"groups_unassigned": 0,
"per_group_file": "gospel-by-province-groups.json — one row per placed group, reconciled with these counts at build time",
"india_note": "India is read from the COMPLETE ADM1 (36 states, geoBoundaries via DataMeet India, CC BY 2.5 IN). The first build of this table read gb-IND-ADM1.geojson, which is the UPG-100 list's curated 34-unit partition, and eleven groups came out with no province because they stood in states that partition does not draw. Switching the file moved 998 assigned to 1,005 and 44 province rows to 46, and turned the province names into India's own states.",
"unassigned_note": "TWO remain, both on China's borders — Jing on the Vietnam coast and Pana near Laos, outside the twelve-province subset. They are listed in `unassigned` with their coordinates. Great Andamanese and the Sentinelese were here too, falling just outside the detailed coastline of islands that ARE in the file; reading the web-sized geometry the interactive maps now fetch put both of them inside Andaman and Nicobar. Measured on the switch: 0 groups moved province, 0 were dropped, 2 were gained.",
"groups_placed_by_nearest_province": 2,
"near_border_note": "NONE unassigned now, and two rows say why. Jing and Pana fell in no province: measured 8.7 km outside Guangxi and 5.4 km outside Yunnan — across the border, not outside the file, which is what an earlier version of this note wrongly said (both provinces are in it). Sourcing China's complete 34-unit ADM1 moved neither distance: same source geometry, 22 more provinces, none of them theirs — so better data is not the answer and nobody need repeat it. A point within 15 km of exactly ONE province now joins it and carries near_border_km; two provinces within the tolerance leave it unassigned, because a hint between two answers is not one. A single coordinate for a whole people is not precise to 9 km, which is why this is a correction and not a guess.",
"china_note": "China's ADM1 file carries 12 provinces, not 31+. Measured rather than assumed: 152 of 154 Chinese groups fall inside them and all twelve are used, so the file is cut to the provinces where the unreached peoples are.",
"ways_in": "Chosen FOR THE PROVINCE where the province can answer: the languages of groups standing in it that already have Scripture AND film, ranked by the people here who already hold them. The column says which basis was used. This replaces a country-level answer that produced a measured absurdity — Arunachal Pradesh, in the far north-east, was offered TELUGU, because the curated bridge table is keyed by country and a curated bridge outranks a computed one. A province with no tier-2 language of its own still falls back to the country answer, and ways_in_basis says so rather than hiding it. It is a NEIGHBOUR, not a recommendation: whether a group actually speaks it is a question for someone on the ground.",
"approximate": "A province is assigned by point-in-polygon on the group's coordinate, and that coordinate marks where a PEOPLE is found, not a settlement — 108 groups across the roster even share a coordinate with another group. Near a border the point can fall on the wrong side. Every row is flagged approximate and the unassigned are counted, never dropped. No surface may present a province as a fact about a group."
@ -91,20 +92,20 @@
"country": "China",
"iso3": "CHN",
"province": "Yunnan Province",
"groups": 61,
"people": 3524875,
"groups": 62,
"people": 3530275,
"zero_access_groups": 14,
"zero_access_people": 602850,
"zero_access_examples": "Hongjin Tai; Wenma Zhuang; Mosuo",
"prayer_pages": 45,
"prayer_pages": 46,
"pray_first": {
"name": "Hongjin Tai",
"url": "https://pray.doxa.life/hongjin-tai?source=doxalife&utm_source=agwm",
"people": 209000,
"has_nothing": true
},
"nobody_praying_groups": 35,
"nobody_praying_people": 1430325,
"nobody_praying_groups": 36,
"nobody_praying_people": 1435725,
"ways_in": "Chinese, Mandarin (63,825 here); Tai Dam (48,000 here)",
"ways_in_basis": "in this province",
"ways_in_country": "Chinese, Mandarin",
@ -321,21 +322,21 @@
"country": "China",
"iso3": "CHN",
"province": "Guangxi Zhuang Autonomous Region",
"groups": 6,
"people": 121150,
"groups": 7,
"people": 150650,
"zero_access_groups": 1,
"zero_access_people": 750,
"zero_access_examples": "Yerong",
"prayer_pages": 5,
"prayer_pages": 6,
"pray_first": {
"name": "Yerong",
"url": "https://pray.doxa.life/yerong?source=doxalife&utm_source=agwm",
"people": 750,
"has_nothing": true
},
"nobody_praying_groups": 4,
"nobody_praying_people": 51150,
"ways_in": "Chinese, Mandarin (50,400 here)",
"nobody_praying_groups": 5,
"nobody_praying_people": 80650,
"ways_in": "Chinese, Mandarin (50,400 here); Vietnamese (29,500 here)",
"ways_in_basis": "in this province",
"ways_in_country": "Chinese, Mandarin",
"approximate": true
@ -1116,20 +1117,5 @@
"approximate": true
}
],
"unassigned": [
{
"country": "China",
"iso3": "CHN",
"people_group": "Jing",
"lat": 21.577774,
"lng": 108.126828
},
{
"country": "China",
"iso3": "CHN",
"people_group": "Pana",
"lat": 21.246492,
"lng": 101.27182
}
]
"unassigned": []
}