leaflet-india-boundary 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.
Files changed (67) hide show
  1. package/LICENSE +25 -0
  2. package/LICENSE-DATA +101 -0
  3. package/README.md +391 -0
  4. package/data/india-boundary.geojson +1 -0
  5. package/dist/cjs/attribution.js +24 -0
  6. package/dist/cjs/attribution.js.map +1 -0
  7. package/dist/cjs/data.js +78 -0
  8. package/dist/cjs/data.js.map +1 -0
  9. package/dist/cjs/index.js +39 -0
  10. package/dist/cjs/index.js.map +1 -0
  11. package/dist/cjs/leaflet.js +152 -0
  12. package/dist/cjs/leaflet.js.map +1 -0
  13. package/dist/cjs/package.json +3 -0
  14. package/dist/cjs/react.js +77 -0
  15. package/dist/cjs/react.js.map +1 -0
  16. package/dist/cjs/style.js +129 -0
  17. package/dist/cjs/style.js.map +1 -0
  18. package/dist/cjs/suppress.js +462 -0
  19. package/dist/cjs/suppress.js.map +1 -0
  20. package/dist/cjs/suppressionData.js +205 -0
  21. package/dist/cjs/suppressionData.js.map +1 -0
  22. package/dist/esm/attribution.d.ts +21 -0
  23. package/dist/esm/attribution.d.ts.map +1 -0
  24. package/dist/esm/attribution.js +21 -0
  25. package/dist/esm/attribution.js.map +1 -0
  26. package/dist/esm/data.d.ts +78 -0
  27. package/dist/esm/data.d.ts.map +1 -0
  28. package/dist/esm/data.js +75 -0
  29. package/dist/esm/data.js.map +1 -0
  30. package/dist/esm/index.d.ts +19 -0
  31. package/dist/esm/index.d.ts.map +1 -0
  32. package/dist/esm/index.js +19 -0
  33. package/dist/esm/index.js.map +1 -0
  34. package/dist/esm/leaflet.d.ts +103 -0
  35. package/dist/esm/leaflet.d.ts.map +1 -0
  36. package/dist/esm/leaflet.js +147 -0
  37. package/dist/esm/leaflet.js.map +1 -0
  38. package/dist/esm/package.json +3 -0
  39. package/dist/esm/react.d.ts +31 -0
  40. package/dist/esm/react.d.ts.map +1 -0
  41. package/dist/esm/react.js +69 -0
  42. package/dist/esm/react.js.map +1 -0
  43. package/dist/esm/style.d.ts +106 -0
  44. package/dist/esm/style.d.ts.map +1 -0
  45. package/dist/esm/style.js +121 -0
  46. package/dist/esm/style.js.map +1 -0
  47. package/dist/esm/suppress.d.ts +64 -0
  48. package/dist/esm/suppress.d.ts.map +1 -0
  49. package/dist/esm/suppress.js +456 -0
  50. package/dist/esm/suppress.js.map +1 -0
  51. package/dist/esm/suppressionData.d.ts +34 -0
  52. package/dist/esm/suppressionData.d.ts.map +1 -0
  53. package/dist/esm/suppressionData.js +202 -0
  54. package/dist/esm/suppressionData.js.map +1 -0
  55. package/dist/style.css +66 -0
  56. package/package.json +113 -0
  57. package/src/attribution.ts +23 -0
  58. package/src/data.ts +153 -0
  59. package/src/index.ts +45 -0
  60. package/src/leaflet.ts +252 -0
  61. package/src/react.ts +92 -0
  62. package/src/style.css +66 -0
  63. package/src/style.ts +168 -0
  64. package/src/suppress.ts +599 -0
  65. package/src/suppressionData.ts +238 -0
  66. package/tools/build-india-boundary.mjs +836 -0
  67. package/tools/postbuild.mjs +61 -0
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abhilaksh Sharma
4
+
5
+ THIS LICENCE COVERS THE CODE ONLY. The boundary geometry — the coordinates in
6
+ src/data.ts and data/india-boundary.geojson — is data, and is licensed separately
7
+ under the Open Database License 1.0. See LICENSE-DATA.
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
package/LICENSE-DATA ADDED
@@ -0,0 +1,101 @@
1
+ DATA LICENCE — Open Database License (ODbL) v1.0
2
+ ================================================
3
+
4
+ This file covers the boundary geometry only:
5
+
6
+ src/data.ts (the coordinates; the surrounding code is MIT)
7
+ data/india-boundary.geojson
8
+ dist/**/data.js (the built copies of the same)
9
+
10
+ The code in this package is MIT-licensed. See LICENSE.
11
+
12
+
13
+ THE LICENCE
14
+ -----------
15
+
16
+ The boundary geometry is made available under the Open Database License v1.0.
17
+
18
+ Full text: https://opendatacommons.org/licenses/odbl/1-0/
19
+ SPDX: ODbL-1.0
20
+
21
+ That text is incorporated here by reference and governs your use of the data.
22
+ The summary below is for orientation only and is not a substitute for it.
23
+
24
+ In short, you are free to copy, distribute, adapt and build on this data,
25
+ including commercially, provided that you:
26
+
27
+ * ATTRIBUTE it (see below);
28
+ * SHARE ALIKE — if you publicly use an adapted version of the database,
29
+ offer that adapted database under ODbL as well;
30
+ * KEEP IT OPEN — do not use technical measures that restrict others'
31
+ ODbL rights in any version you distribute.
32
+
33
+
34
+ ORIGIN
35
+ ------
36
+
37
+ The boundary geometry originates with shivrajgodle (github.com/shivrajgodle), who
38
+ posted it to Leaflet/Leaflet#8943 in February 2025:
39
+
40
+ https://github.com/Leaflet/Leaflet/issues/8943#issuecomment-2655497924
41
+
42
+ Please keep that credit with the data.
43
+
44
+
45
+ ATTRIBUTION
46
+ -----------
47
+
48
+ Credit OpenStreetMap wherever the boundary is shown:
49
+
50
+ India boundary: © OpenStreetMap contributors (ODbL), partly hand-carried
51
+
52
+ The Leaflet layer factories in this package set this attribution by default, so
53
+ an application using them is compliant without further action.
54
+
55
+ Attributing your basemap tiles to OpenStreetMap does NOT discharge this. The
56
+ tile credit covers the basemap imagery; this is a separate extract of the same
57
+ database, drawn as a separate layer.
58
+
59
+
60
+ PROVENANCE, STATED PRECISELY
61
+ ----------------------------
62
+
63
+ The 67 features in this dataset are not uniform in origin, and the difference is
64
+ recorded per feature in `properties.source`. This matters for licensing, so it is
65
+ spelled out here rather than left to the README:
66
+
67
+ 36 features tagged "osm"
68
+ OpenStreetMap independently places a boundary along these. 30 of the 36
69
+ match OSM's geometry exactly. These are OSM-derived, and the ODbL above is
70
+ the operative licence for them.
71
+
72
+ 31 features tagged "hand"
73
+ OpenStreetMap has no boundary there at all — the nearest is 14.3 km away at
74
+ best, 180 km at worst. Their provenance is the coordinate list shivrajgodle
75
+ (github.com/shivrajgodle) posted to Leaflet/Leaflet#8943 in February 2025.
76
+ They most likely worked from OSM, but that is an assumption, not a record.
77
+
78
+ They are offered under ODbL along with the rest, conservatively, on that
79
+ assumption. If the assumption is wrong, the licence is stricter than it
80
+ needed to be rather than looser.
81
+
82
+
83
+ WHAT THIS LICENCE DOES NOT SAY
84
+ ------------------------------
85
+
86
+ * It is NOT an endorsement by OpenStreetMap, by the OpenStreetMap Foundation,
87
+ or by any OSM contributor, of the boundary depicted here. ODbL is a licence
88
+ on data, not an opinion about it. OSM's own default rendering deliberately
89
+ shows administrative control rather than claims, which is why this overlay
90
+ exists at all. Do not represent the attribution above as OSM agreeing with
91
+ this depiction.
92
+
93
+ * It says nothing about the 31 "hand" features being accurate. They are
94
+ unverified. `properties.source` exists so that a project which cannot accept
95
+ unverified geometry can identify and filter them.
96
+
97
+ * It is not a statement about which depiction of this region is correct, or
98
+ lawful, anywhere. This dataset follows the Survey of India depiction. Other
99
+ states depict the region differently. Whether that is the right choice for a
100
+ given application, in a given jurisdiction, is a question for that
101
+ application's own counsel.
package/README.md ADDED
@@ -0,0 +1,391 @@
1
+ # leaflet-india-boundary
2
+
3
+ India's boundary through **Jammu & Kashmir, Ladakh and Aksai Chin** — as depicted by the Survey of India — as a drop-in overlay for Leaflet, styled to match OpenStreetMap's own national borders rather than painted on top of them.
4
+
5
+ The GeoJSON and the style values are framework-agnostic and exported separately, so MapLibre GL, OpenLayers and deck.gl users can take those without installing Leaflet.
6
+
7
+ ![Stock OpenStreetMap tiles on the left; the same view with leaflet-india-boundary on the right, adding India's claimed boundary through Jammu & Kashmir, Ladakh and Aksai Chin in the basemap's own border style](https://raw.githubusercontent.com/lux-in-tenebris-lucet/leaflet-india-boundary/main/docs/hero.png)
8
+
9
+ **[Live demo](https://lux-in-tenebris-lucet.github.io/leaflet-india-boundary/)**
10
+
11
+ ```bash
12
+ npm install leaflet-india-boundary
13
+ ```
14
+
15
+ ```js
16
+ import { indiaBoundaryLayer } from "leaflet-india-boundary/leaflet";
17
+
18
+ indiaBoundaryLayer().addTo(map);
19
+ ```
20
+
21
+ That is the whole integration. The layer restyles itself on zoom to keep matching the tiles underneath, sets its own ODbL attribution, and ignores pointer events so it never swallows a click meant for a marker.
22
+
23
+ ---
24
+
25
+ ## Contents
26
+
27
+ - [Why this is a package and not an upstream fix](#why-this-is-a-package-and-not-an-upstream-fix)
28
+ - [What it cannot do](#what-it-cannot-do)
29
+ - [Usage](#usage)
30
+ - [If your basemap is filtered — read this one](#if-your-basemap-is-filtered--read-this-one)
31
+ - [Removing the basemap's own line (experimental)](#removing-the-basemaps-own-line-experimental)
32
+ - [Where the style numbers come from](#where-the-style-numbers-come-from)
33
+ - [Provenance: what `properties.source` means](#provenance-what-propertiessource-means)
34
+ - [Regenerating from OpenStreetMap](#regenerating-from-openstreetmap)
35
+ - [API](#api)
36
+ - [Licence and attribution](#licence-and-attribution)
37
+
38
+ ---
39
+
40
+ ## Why this is a package and not an upstream fix
41
+
42
+ Leaflet's default OpenStreetMap tiles do not show India's official boundary. In Jammu & Kashmir, Ladakh and Aksai Chin they draw the Line of Control and the Line of Actual Control — the lines of physical control — so an Indian user looking at a stock Leaflet map sees a border their government does not recognise.
43
+
44
+ [Leaflet/Leaflet#8943](https://github.com/Leaflet/Leaflet/issues/8943) asked for this to be fixed and was closed as not-planned. **That was the right call.** Leaflet is a rendering library: it draws whatever tiles you hand it and has no opinion about what is in them. There is no line of code in Leaflet that could fix this.
45
+
46
+ OpenStreetMap's default style will not change either, and for a reason worth understanding rather than resenting. OSM maps administrative boundaries on the "on the ground" principle — what is actually administered, not what is claimed — and the same tile set is served to users in India, Pakistan and China simultaneously. There is no single rendering of this region that satisfies all three, so OSM renders control and leaves claims to be tagged separately.
47
+
48
+ Which leaves the fix where it actually belongs: in the application, per audience. This is exactly what commercial providers sell as a *political view* — HERE's `pview=IN` parameter, MapTiler's worldview setting. This package is that feature, for the free OSM raster tiles that most Leaflet apps use.
49
+
50
+ ## What it cannot do
51
+
52
+ **This overlay adds India's claimed border. It cannot remove the LoC/LAC line already drawn in the tiles.** By the time a raster tile reaches the browser it is a picture; nothing drawn on top of it can erase part of it. Where the two lines run far apart — most visibly across Aksai Chin — you will see both.
53
+
54
+ Removing the basemap's own line requires one of:
55
+
56
+ - **Vector tiles**, where the boundary layer is data and can be filtered or restyled (MapLibre GL, or Leaflet with a vector-tile plugin).
57
+ - **A provider worldview** — [Mapbox Boundaries](https://docs.mapbox.com/data/boundaries/reference/mapbox-boundaries-v4/) has an Indian worldview, HERE has `pview=IN` — which renders correct tiles server-side and needs no overlay at all.
58
+
59
+ If you are already on vector tiles, filtering the basemap's boundary layer and drawing this GeoJSON over it gets you a single correct line. On raster tiles, two lines is the ceiling. It is still a large improvement on a map with no Indian border at all, but it is worth knowing before you ship, and worth telling your designer.
60
+
61
+ ### It also cannot touch labels, which may matter more
62
+
63
+ The same "on the ground" rule that places the border also picks the **label language and toponymy**. On standard OSM raster tiles over this region you will see Gilgit-Baltistan, Skardu and Muzaffarabad labelled in Urdu, and Aksai Chin labelled in Chinese, because those are the local `name` tags where those areas are administered from.
64
+
65
+ This package does nothing about that, and on raster tiles nothing much can. The border was suppressible in principle because it is a known one-dimensional geometry — you can rasterise a mask along it. Labels are not: openstreetmap-carto places them at render time with a collision-avoidance engine, so their position is not predictable from the OSM data. Without reimplementing that engine there is no mask to build, and OCR-ing your own basemap is not a serious plan.
66
+
67
+ So, plainly: **this is not a compliance tool.** It adds India's claimed boundary in the basemap's own style, which is what it is for. It does not produce a map that matches the official depiction, because it cannot remove the LoC/LAC line and cannot change a single label. If you need the official depiction end to end — most obviously for a consumer product published in India — use a provider worldview, which renders boundaries *and* labels for an Indian audience server-side. That is precisely the feature those products exist to sell, and it is a configuration parameter rather than a project. Whether and how any of this applies to you is a question for your own counsel, not for a README.
68
+
69
+ ## Usage
70
+
71
+ ### Leaflet
72
+
73
+ ```js
74
+ import L from "leaflet";
75
+ import { indiaBoundaryLayer } from "leaflet-india-boundary/leaflet";
76
+
77
+ const map = L.map("map").setView([22, 79], 5);
78
+ L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
79
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
80
+ }).addTo(map);
81
+
82
+ indiaBoundaryLayer().addTo(map);
83
+ ```
84
+
85
+ ### react-leaflet
86
+
87
+ ```tsx
88
+ import { MapContainer, TileLayer } from "react-leaflet";
89
+ import { IndiaBoundary } from "leaflet-india-boundary/react";
90
+
91
+ <MapContainer center={[22, 79]} zoom={5}>
92
+ <TileLayer url="https://tile.openstreetmap.org/{z}/{x}/{y}.png" />
93
+ <IndiaBoundary />
94
+ </MapContainer>;
95
+ ```
96
+
97
+ ### MapLibre GL / Mapbox GL
98
+
99
+ The root export has no Leaflet dependency:
100
+
101
+ ```js
102
+ import {
103
+ INDIA_BOUNDARY,
104
+ OSM_ADMIN_BOUNDARY_COLOR,
105
+ osmAdminBoundaryWidthExpression,
106
+ } from "leaflet-india-boundary";
107
+
108
+ map.addSource("india-boundary", { type: "geojson", data: INDIA_BOUNDARY });
109
+ map.addLayer({
110
+ id: "india-boundary",
111
+ type: "line",
112
+ source: "india-boundary",
113
+ minzoom: 4, // see the zoom gate below
114
+ paint: {
115
+ "line-color": OSM_ADMIN_BOUNDARY_COLOR,
116
+ "line-width": osmAdminBoundaryWidthExpression(),
117
+ "line-opacity": 0.5,
118
+ },
119
+ layout: { "line-join": "bevel" },
120
+ });
121
+ ```
122
+
123
+ ### OpenLayers
124
+
125
+ ```js
126
+ import GeoJSON from "ol/format/GeoJSON.js";
127
+ import VectorLayer from "ol/layer/Vector.js";
128
+ import VectorSource from "ol/source/Vector.js";
129
+ import Stroke from "ol/style/Stroke.js";
130
+ import Style from "ol/style/Style.js";
131
+ import { INDIA_BOUNDARY, osmAdminBoundaryStyle } from "leaflet-india-boundary";
132
+
133
+ const layer = new VectorLayer({
134
+ source: new VectorSource({
135
+ features: new GeoJSON().readFeatures(INDIA_BOUNDARY, {
136
+ featureProjection: "EPSG:3857",
137
+ }),
138
+ }),
139
+ style: (_feature, resolution) => {
140
+ const zoom = map.getView().getZoomForResolution(resolution);
141
+ const { color, width, opacity, visible } = osmAdminBoundaryStyle(zoom);
142
+ if (!visible) return undefined;
143
+ return new Style({ stroke: new Stroke({ color, width, lineJoin: "bevel" }) });
144
+ },
145
+ });
146
+ ```
147
+
148
+ ### Anything else
149
+
150
+ A plain GeoJSON file ships in the package for QGIS, Python, or a runtime `fetch`:
151
+
152
+ ```
153
+ node_modules/leaflet-india-boundary/data/india-boundary.geojson
154
+ ```
155
+
156
+ It carries its licence, attribution and per-feature `source` tags as top-level members, so it stays self-describing once separated from this repo.
157
+
158
+ ## If your basemap is filtered — read this one
159
+
160
+ **Skip this section if your basemap is stock OSM raster tiles.** Everything below concerns apps that apply a CSS filter to their tile pane, which is the usual way to build a dark basemap out of OSM's light tiles:
161
+
162
+ ```css
163
+ .leaflet-tile-pane {
164
+ filter: invert(1) hue-rotate(192deg) brightness(0.95) contrast(0.86) saturate(0.45);
165
+ }
166
+ ```
167
+
168
+ If that is you, pass the same filter to the layer:
169
+
170
+ ```js
171
+ indiaBoundaryLayer({
172
+ tileFilter: "invert(1) hue-rotate(192deg) brightness(0.95) contrast(0.86) saturate(0.45)",
173
+ }).addTo(map);
174
+ ```
175
+
176
+ Two separate things go wrong without it, and both are confusing enough to be worth spelling out.
177
+
178
+ **The colour lands nowhere near the tiles.** Your filter transforms the tile pane; the overlay pane is not filtered. So a purple lifted from OSM's stylesheet is correct *inside the tile* and wrong on screen — the border you drew and the borders in the tiles are now different colours, which is precisely the thing this package exists to avoid. Passing `tileFilter` puts the overlay through the same transform, and they match again.
179
+
180
+ **The line disappears.** openstreetmap-carto draws borders at `opacity: 0.5`, but it composites that 50% against its own light land fill (`#f2efe9`) *inside the tile*, before any filter of yours runs. Honouring 0.5 on the unfiltered side of an inverting filter, over a near-black map, lands roughly three times too dark. Matching carto exactly was tried in the app this was extracted from and resolves to `#5b5157` — genuinely indistinguishable from OSM's own borders, and therefore genuinely invisible, because a dark basemap crushes every border including OSM's.
181
+
182
+ So `tileFilter` also raises the default `opacity` from carto's `0.5` to `1`. Full opacity keeps carto's hue while staying legible, which is the entire point of drawing the line. Override `opacity` explicitly if you disagree.
183
+
184
+ Implementation note: the filter goes on a dedicated map pane the layer creates, so it works under both the SVG and Canvas renderers. If you pass your own `pane`, the filter is applied to *that* pane and therefore to everything else in it — pass a pane you own. There is also a CSS route via `--india-boundary-filter`; see [`src/style.css`](src/style.css). Use one or the other, not both.
185
+
186
+ ## Removing the basemap's own line (experimental)
187
+
188
+ `leaflet-india-boundary/suppress` edits the tiles in a canvas to erase the LoC and LAC before Leaflet shows them. Compose it with the overlay and you get one line instead of two on free raster tiles:
189
+
190
+ ```js
191
+ import { suppressedTileLayer } from "leaflet-india-boundary/suppress";
192
+ import { indiaBoundaryLayer } from "leaflet-india-boundary/leaflet";
193
+
194
+ suppressedTileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
195
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
196
+ }).addTo(map);
197
+
198
+ indiaBoundaryLayer().addTo(map);
199
+ ```
200
+
201
+ **It still does not touch labels**, so everything in [the section above](#it-also-cannot-touch-labels-which-may-matter-more) stands. This closes the border half of the gap, not the whole gap.
202
+
203
+ ### Why it is not colour detection
204
+
205
+ The obvious approach — find carto's border purple and paint over it — does not work, and the reason is worth knowing before you try it. carto draws admin boundaries at `opacity: 0.5`, so what lands in the tile is a *blend* of the border with whatever terrain is underneath. There is no border colour to look for. Measured on one z6 tile over Kashmir: 69 pixels in the whole tile sit within a small distance of `#8d618b`, and widening the threshold enough to catch the rest starts matching a river (`#999bc4`) and a road (`#9b9a96`) instead.
206
+
207
+ So colour cannot say *what*. Geometry has to say *where* — and the geometry is free, because it is exactly what deriving India's boundary throws away. A boundary way in two of the four disputed regions is the Line of Control; a way shared with China is the Line of Actual Control. The derivation discards both to leave India's claimed outer edge, and `suppressionData.ts` keeps them: 78 LoC features and 75 LAC features, from the same database the tiles were rendered from, so they line up by construction.
208
+
209
+ Per tile: rasterise those into a ribbon at carto's own stroke width for the zoom, then inpaint underneath it by repeatedly averaging each masked pixel's unmasked neighbours, working inward from the ribbon's edges. Terrain reconstructs well because the ribbon is thin and the ground either side of it is locally similar.
210
+
211
+ Measured on the example page at z6, against the same tiles fetched unmodified: 3,110 pixels changed on the tile the line crosses most (4.75%), and those pixels were on average **1.6× closer to carto's admin purple** than a random pixel in the tile (89.8 vs 137.6). A tile inside the bbox that the line does not actually cross had **zero** pixels changed.
212
+
213
+ ### Cost, and the caveats
214
+
215
+ It is cheap because almost no tiles are affected. The disputed region is one bbox, and every tile outside it takes a fast path returning a plain `<img>` — no canvas, no pixel readback. Call `debugMask: true` to paint the mask instead of inpainting under it, which is how you check the ribbon lands on the line before trusting what it removes.
216
+
217
+ | caveat | detail |
218
+ | --- | --- |
219
+ | Labels untouched | The reason this is not a compliance route. See above. |
220
+ | Things crossing the line get inpainted too | A road or glyph overlapping the ribbon is reconstructed from its surroundings. Keeping the ribbon at carto's real stroke width rather than padding it generously is what keeps this rare — going from a 6 px ribbon to 3 px was the difference between "Jammu" reading as `ammu` and reading correctly. |
221
+ | Tile-edge seams handled | Inpainting tiles in isolation makes neighbours disagree along the join. The work happens on a padded canvas seeded with the neighbour tiles the ribbon actually runs into — usually two or three, and normally already in the browser cache. Measured across a shared edge: the processed join differs by 2.28 mean RGB against the raw tiles' own 6.15, i.e. smoother than the originals. Turn off with `seamlessEdges: false`. |
222
+ | Needs CORS | `getImageData` requires `Access-Control-Allow-Origin`. `tile.openstreetmap.org` sends it. If yours does not, the tile is shown unmodified rather than failing — see `fallbackOnError`. |
223
+ | Re-done per load | The browser caches the original tile, not the edited one. |
224
+
225
+ | option | default | description |
226
+ | --- | --- | --- |
227
+ | `ribbonWidth` | carto's stroke + antialiasing + simplification allowance | number, or a function of zoom. Widen if a sliver survives; narrow if labels are being chewed. |
228
+ | `fallbackOnError` | `true` | show the tile unmodified on a CORS failure |
229
+ | `debugMask` | `false` | paint the mask magenta instead of inpainting |
230
+ | `seamlessEdges` | `true` | seed the inpaint with neighbour tiles so reconstructions match across tile joins |
231
+
232
+ All other `TileLayer` options pass through.
233
+
234
+ And the honest framing: this is **un-rendering**. You are spending per-tile pixel work to undo a render that OSM would simply have skipped if you had asked for vector tiles. It is the right tool only if you are committed to raster and the double line is unacceptable.
235
+
236
+ ## Where the style numbers come from
237
+
238
+ Every value is transcribed from openstreetmap-carto's `style/admin.mss` — the stylesheet that renders the tiles themselves — rather than chosen by eye. This is most of what makes the overlay read as *part of the map* instead of an annotation drawn over it. A border in a slightly different purple, or a pixel too heavy, looks like something a developer added. The same border in the basemap's own colour and weight looks like the map.
239
+
240
+ | | value | carto source |
241
+ | --- | --- | --- |
242
+ | Colour, z4–z9 | `#8d618b` | `@admin-boundaries` |
243
+ | Colour, z10+ | `#a37da1` | `@admin-boundaries-wide` |
244
+ | Opacity | `0.5` | `::wideline { opacity }` |
245
+ | Join | `bevel` | `::wideline` |
246
+
247
+ Width by zoom, from carto's `::wideline` attachment:
248
+
249
+ | zoom | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
250
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
251
+ | width | 1.2 | 1.5 | 1.8 | 2.2 | 3 | 3.5 | 4.5 | 5 | 6 | 7 | 8 |
252
+
253
+ Two details in there are easy to get wrong:
254
+
255
+ **There is no white casing.** carto pairs its border with a white "background" line, and it is tempting to copy. Don't — it is a `comp-op: darken` trick for hiding the seam where two countries' borders overlap in the same tile. On a single overlay there is no seam to hide, and a white line under a purple one just looks like a mistake.
256
+
257
+ **The boundary must vanish below zoom 4.** carto's national-border rule starts at `[zoom >= 4]`, so below that the basemap draws no country borders at all. An overlay that keeps drawing is a lone purple squiggle floating over a borderless world map, attached to nothing and unmistakably wrong. The Leaflet layer handles this; MapLibre users want `minzoom: 4`; everyone else can ask `isOsmAdminBoundaryVisible(zoom)`.
258
+
259
+ ## Provenance: what `properties.source` means
260
+
261
+ Every one of the 67 features carries `properties.source`, either `"osm"` or `"hand"`. This is the part of the package worth your attention, because it is the part that tells you how much to trust the line you are about to show your users.
262
+
263
+ These coordinates are **[shivrajgodle's](https://github.com/shivrajgodle)**, posted in [a comment on that issue](https://github.com/Leaflet/Leaflet/issues/8943#issuecomment-2655497924) in February 2025. Every feature here still traces back to that list — the first one is byte-for-byte identical — and the entire package exists because they did the work of writing the line down in the first place.
264
+
265
+ What was missing was a way to check it: a coordinate list in an issue thread tells you nothing about where it came from. So the boundary was re-derived from OpenStreetMap ([`tools/build-india-boundary.mjs`](tools/build-india-boundary.mjs)) and each feature tagged with whether that derivation backs it up. The tags are an audit of the data, not a criticism of its author.
266
+
267
+ | tag | features | meaning |
268
+ | --- | --- | --- |
269
+ | `osm` | 36 | OSM independently places a boundary along this feature. 30 of the 36 match OSM's geometry exactly; the furthest is 5.1 km off. |
270
+ | `hand` | 31 | It does not. The nearest OSM boundary is 14.3 km away at best, 180 km at worst. |
271
+
272
+ **The split is not arbitrary, and its cause is the interesting part.**
273
+
274
+ The **western** edge — Azad Kashmir and Gilgit-Baltistan against Pakistan and Afghanistan — is simultaneously India's claim *and* a real administered boundary that exists on the ground. OSM maps it the way it maps any border. That is almost the whole `osm` set.
275
+
276
+ The **eastern and northern** edges have no administered counterpart for anyone to survey. The Aksai Chin claim line, and India's claim north of the Sino-Pakistan line across the Shaksgam Valley / Trans-Karakoram Tract, exist only as claims — so OSM carries fragments at best. A targeted `claimed_by` query in the far north returns two ways. Those stretches are the `hand` set, and they **cannot currently be sourced from OSM at all**.
277
+
278
+ Note the gap between the two ranges above: 5.1 km to 14.3 km, with no feature in it. The groups are separated by a factor of 2.8, so the classification is a fact about the data rather than an artefact of where a threshold was placed — any cutoff between 6 km and 14 km produces exactly these 36 and these 31.
279
+
280
+ Two decisions follow, and both are deliberate:
281
+
282
+ - **The `hand` features are not stripped.** A dataset showing only what OSM can corroborate would look tidier and be worse: the `hand` features are what makes the line continuous, and Shaksgam is precisely the territory this overlay exists to show as India's.
283
+ - **The `hand` features are not rendered differently.** Every feature draws identically. The tag is a note to whoever maintains the data, not a statement about the border, and a map that drew part of a national boundary as provisional would be making a claim of its own.
284
+
285
+ If you want to improve this, the useful work is upstream: get the missing claim line mapped in OSM, then re-run the generator and watch features flip from `hand` to `osm`.
286
+
287
+ ## Regenerating from OpenStreetMap
288
+
289
+ ```bash
290
+ node tools/build-india-boundary.mjs --retag # re-check the source tags
291
+ node tools/build-india-boundary.mjs --retag --write # …and apply any changes
292
+ node tools/build-india-boundary.mjs --cache # reuse the last download
293
+ ```
294
+
295
+ The derivation needs no geometry library. India's claim is the *outer* edge of the union of four regions, and that edge falls out of way membership alone:
296
+
297
+ - A boundary way belonging to **two** of the four regions is internal — which is exactly the Line of Control, so it drops out on its own.
298
+ - A way belonging to **one** is on the outer edge, then classified by its neighbour: shared with an Indian state → internal, drop; shared with China → that is the LAC, control not claim, drop; otherwise (Pakistan, Afghanistan) → India's claimed border, keep.
299
+
300
+ The dropped China stretch is refilled from ways tagged `claimed_by=IN`.
301
+
302
+ One thing the membership test cannot see, and which is worth knowing if you adapt this: the southern Line of Control — the stretch running east from near Jammu — has Pakistani *Punjab province* on the far side rather than Azad Kashmir. It therefore belongs to only one of the four regions, never trips the shared-by-two test, and used to come out classified as India's claimed border: 12 ways of Line of Control drawn as the claim, and missing from the suppression mask. OSM tags it explicitly (`disputed=yes`, `disputed_by=IN;PK`), so the tool now asks rather than infers. The legitimate claim edge — the Durand Line, and Azad Kashmir's and Gilgit-Baltistan's boundaries with Pakistan proper — carries no `disputed` tag at all, which is what makes the test safe.
303
+
304
+ **The tool's output is not yet a drop-in replacement for the shipped data**, and it says so when you run it. It produces ~11x the coordinate detail (7,539 points against 689) and discards the LoC correctly, but the refilled China sector does not join the region-derived ways cleanly — the last run left 9 dangling endpoints — and the northern claim is barely in OSM to begin with. Rendering it as-is draws a visibly broken border. A continuous line beats a more precise broken one, so the shipped data stays until endpoint snapping is added. Read the `STATUS` block at the top of the script before wiring its output into anything.
305
+
306
+ What the tool *is* reliable for today is `--retag`: checking whether OSM has caught up.
307
+
308
+ Three practical notes, each of which cost an afternoon:
309
+
310
+ - **Overpass returns 406 to requests with no `User-Agent`,** and Node's `fetch` sends none. The failure is indistinguishable from a query syntax error. POST fails identically; the script uses GET.
311
+ - **The public instance answers 504 when busy.** That is a queue, not a broken query. Use `--endpoint https://overpass.kumi.systems/api/interpreter` or set `OVERPASS_URL`, then `--cache` afterwards.
312
+ - **Do not widen `CLAIM_WAY_BBOX` past roughly lat 36.2 / lon 80.6.** It starts dragging in the Himachal–Tibet sector, which is a separate dispute that does not connect to this ring.
313
+
314
+ ## API
315
+
316
+ ### `leaflet-india-boundary` — no mapping dependency
317
+
318
+ | export | description |
319
+ | --- | --- |
320
+ | `INDIA_BOUNDARY` | `FeatureCollection<LineString, { source }>`, 67 features, 689 points |
321
+ | `INDIA_BOUNDARY_COUNTS` | `{ osm: 36, hand: 31 }` |
322
+ | `INDIA_BOUNDARY_ATTRIBUTION` | the ODbL credit, with markup |
323
+ | `INDIA_BOUNDARY_ATTRIBUTION_TEXT` | the same, plain text |
324
+ | `osmAdminBoundaryStyle(zoom, opts?)` | `{ color, width, opacity, visible }` |
325
+ | `osmAdminBoundaryColor(zoom, opts?)` | carto's colour, handling the z10 swap |
326
+ | `osmAdminBoundaryWidth(zoom)` | carto's width, clamped to its table |
327
+ | `isOsmAdminBoundaryVisible(zoom, opts?)` | whether the basemap draws borders here |
328
+ | `osmAdminBoundaryWidthExpression()` | the width table as a MapLibre expression |
329
+ | `OSM_ADMIN_STYLE` | every carto value in one frozen object |
330
+
331
+ Individual constants — `OSM_ADMIN_BOUNDARY_COLOR`, `…_COLOR_WIDE`, `…_WIDTH`, `…_OPACITY`, `…_MIN_ZOOM`, `…_WIDE_ZOOM`, `…_MAX_TABLE_ZOOM` — are exported too.
332
+
333
+ ### `leaflet-india-boundary/leaflet`
334
+
335
+ `indiaBoundaryLayer(options?)` returns an `L.GeoJSON`.
336
+
337
+ | option | default | description |
338
+ | --- | --- | --- |
339
+ | `tileFilter` | — | CSS filter to match a filtered tile pane. [Read this](#if-your-basemap-is-filtered--read-this-one). Also raises `opacity` to 1. |
340
+ | `opacity` | `0.5`, or `1` with `tileFilter` | stroke opacity |
341
+ | `color` | `#8d618b` | colour below `wideZoom` |
342
+ | `colorWide` | `#a37da1` | colour from `wideZoom` up |
343
+ | `wideZoom` | `10` | where the colour swaps |
344
+ | `minZoom` | `4` | below this, nothing is drawn |
345
+ | `className` | — | extra class(es) on each path |
346
+ | `pane` | `overlayPane`, or an owned pane with `tileFilter` | map pane |
347
+ | `attribution` | the ODbL credit | `false` to suppress |
348
+ | `interactive` | `false` | whether the border takes pointer events |
349
+
350
+ Also exports `indiaBoundaryElements(layer)` for reaching the live SVG elements, and the class-name constants.
351
+
352
+ ### `leaflet-india-boundary/react`
353
+
354
+ `<IndiaBoundary {...options} />` — same options as props. Requires `react-leaflet`, renders no DOM of its own.
355
+
356
+ ### `leaflet-india-boundary/suppress`
357
+
358
+ `suppressedTileLayer(urlTemplate, options?)` returns a `L.TileLayer` that erases the LoC/LAC from its tiles. See [the section above](#removing-the-basemaps-own-line-experimental). Also exports `INDIA_BOUNDARY_SUPPRESSION` (153 features tagged `loc` or `lac`) and `SUPPRESSION_BBOX`. Kept on its own subpath because the geometry is ~65 kB gzipped and nobody should pay for it who is not using it.
359
+
360
+ ### `leaflet-india-boundary/style.css`
361
+
362
+ Optional. You do not need it: the layer sets colour, width and opacity as SVG attributes. Import it only to drive styling from CSS instead, and note that a CSS `stroke` beats the SVG attribute, so a colour rule there silently wins over the `color` option.
363
+
364
+ ## Licence and attribution
365
+
366
+ Two licences, because there are two kinds of thing in this package.
367
+
368
+ **Code — MIT.** See [`LICENSE`](LICENSE). Everything in `src/` that is not coordinates, plus `tools/`.
369
+
370
+ **Data — ODbL 1.0.** See [`LICENSE-DATA`](LICENSE-DATA). The boundary geometry is treated as derived from OpenStreetMap, which is ODbL-licensed and requires attribution wherever the data is shown. The Leaflet layer sets that attribution for you by default, so you are compliant without having read this section — which is the point. If you use the raw GeoJSON with another renderer, carry the credit yourself:
371
+
372
+ > India boundary: © OpenStreetMap contributors (ODbL), partly hand-carried
373
+
374
+ Attributing your basemap tiles to OpenStreetMap does not cover this. The tile credit is for the basemap; this is a separate extract of the same database.
375
+
376
+ **On the 31 `hand` features specifically.** They have no OSM counterpart, so calling them OSM-derived would be inaccurate — but their provenance is a comment on a public issue thread, which is murkier still. They are licensed as ODbL along with the rest, conservatively, on the assumption that whoever produced them worked from OSM. What matters more than the licence label:
377
+
378
+ - **OpenStreetMap and its contributors do not vouch for these features.** The ODbL attribution above credits the database the corroborated portion came from. It is not an endorsement by OSM of this depiction, and must not be presented as one.
379
+ - **They are not independently verified.** If your use is one where an unverified boundary is a real problem, `properties.source` is there so you can filter or flag them.
380
+
381
+ **On the depiction itself.** This renders India's official boundary as depicted by the Survey of India, India's national mapping agency. Other states depict this region differently, and this package takes no position on the underlying disputes — it does the same job a commercial provider's political view does, for free tiles. Which depiction is appropriate depends on your audience and where you publish; if that question carries legal weight for you, it is one for your own counsel and not for a README.
382
+
383
+ ## Credits
384
+
385
+ The boundary geometry originates with **[shivrajgodle](https://github.com/shivrajgodle)**, who posted it in [Leaflet/Leaflet#8943](https://github.com/Leaflet/Leaflet/issues/8943#issuecomment-2655497924) in February 2025 as a working answer for anyone hitting the same problem. Everything here is built on that: the derivation tool, the provenance tags and the carto-matched styling are scaffolding around their line.
386
+
387
+ Boundary data © OpenStreetMap contributors where OSM corroborates it — see [Licence and attribution](#licence-and-attribution).
388
+
389
+ ## Contributing
390
+
391
+ The most valuable contribution is upstream, in OpenStreetMap: every stretch of the claim line that gets mapped there is a feature that can flip from `hand` to `osm`. After that, endpoint snapping in the derivation tool (see its `STATUS` block) is what stands between the 689-point shipped file and the 7,539-point derived one.
@@ -0,0 +1 @@
1
+ {"type":"FeatureCollection","name":"india-boundary","description":"India's boundary through Jammu & Kashmir, Ladakh and Aksai Chin, as depicted by the Survey of India. Each feature's properties.source records whether OpenStreetMap independently corroborates it ('osm') or not ('hand').","license":"ODbL-1.0","attribution":"India boundary: © OpenStreetMap contributors (ODbL), partly hand-carried","source_counts":{"osm":36,"hand":31},"homepage":"https://github.com/lux-in-tenebris-lucet/leaflet-india-boundary","features":[{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.7541731,32.9466018],[73.8076029,32.9423743],[73.8313682,32.9287784]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.6713786,33.2047351],[73.6755463,33.1954493],[73.6629287,33.1586631],[73.6375765,33.1539475],[73.6300569,33.1355147],[73.6355715,33.1041905],[73.6553984,33.0780875],[73.6997514,33.0736643],[73.7346844,33.064026]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.7511423,33.0500452],[73.7452654,33.0346563],[73.7608652,33.0167016],[73.7602995,32.9945073],[73.784266,32.9666077],[73.7541731,32.9466018]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.7346844,33.064026],[73.7511423,33.0500452]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.126488,35.124629],[74.080276,35.151099],[74.044984,35.141381],[73.994706,35.176384],[73.955261,35.183326],[73.930268,35.20305],[73.891099,35.211663],[73.849716,35.207772],[73.804703,35.236106],[73.781098,35.240546],[73.730545,35.21971],[73.721374,35.229431],[73.699706,35.297219],[73.697206,35.362769],[73.709426,35.413605],[73.721923,35.42749],[73.764436,35.449433],[73.80304,35.503052],[73.780273,35.522491],[73.741653,35.522218],[73.726379,35.529716],[73.673035,35.532768],[73.570832,35.555267],[73.534715,35.553321],[73.493043,35.568603],[73.414429,35.573325],[73.347488,35.599435],[73.295823,35.607498],[73.23027,35.643883],[73.2061,35.665268],[73.157212,35.69027],[73.126084,35.722763],[73.113038,35.751107],[73.114989,35.79222],[73.124696,35.82888],[73.114989,35.847772],[73.092483,35.860275],[73.058868,35.861107],[73.008332,35.840827],[72.976379,35.833878],[72.878312,35.843605],[72.825272,35.856102],[72.800537,35.854714],[72.764435,35.838883],[72.688583,35.818603],[72.670822,35.81833],[72.618867,35.837769],[72.570267,35.848601],[72.549148,35.881104],[72.5107111,35.8960973]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[72.7698226,36.3078837],[72.8053899,36.3432476],[72.8558265,36.373232],[72.8756919,36.3906618],[72.8671272,36.4113425],[72.8785468,36.4385255],[72.9047167,36.4593849],[72.9264853,36.4530703],[72.9474213,36.4603416],[72.9728775,36.4545054],[72.9862003,36.4680906],[72.9747807,36.4946799],[72.9798958,36.5123692],[73.0511493,36.5322529],[73.0586435,36.5678969],[73.0764866,36.5713362],[73.0802931,36.5939744],[73.0969467,36.6067712],[73.0760108,36.6213798],[73.0689925,36.6792141],[73.1086042,36.6856057],[73.1568996,36.7084011],[73.1739101,36.7265182],[73.2377885,36.707066],[73.3082094,36.7030605],[73.3707793,36.7334778],[73.4151493,36.7326198],[73.4435793,36.7211788],[73.4579728,36.7360517],[73.5000826,36.7134552],[73.5631283,36.7162206],[73.579544,36.7243253],[73.5948891,36.6948582],[73.6398538,36.6981011],[73.6900524,36.6907567],[73.7090851,36.713646],[73.7990145,36.6997225],[73.8241139,36.7115481],[73.8619413,36.6994363],[73.8825204,36.7033466],[73.8890629,36.7282343],[73.8818066,36.7692193],[73.8482616,36.7917994],[73.8151923,36.7991341],[73.7955648,36.8196105],[73.7641609,36.8146586],[73.7478642,36.8272282],[73.7050407,36.8351307],[73.654106,36.908791]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.5862409,33.4257503],[73.590003,33.412472],[73.5663406,33.3718378],[73.5724287,33.3425105],[73.5956654,33.3240186],[73.6103332,33.2882871]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.6103332,33.2882871],[73.6155971,33.2479462]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.6155971,33.2479462],[73.6366118,33.210103],[73.6713786,33.2047351]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.5733914,33.6752806],[73.5670237,33.6668826],[73.5839324,33.6174785],[73.6054284,33.5888159],[73.629182,33.5695089],[73.6225731,33.5438847],[73.6387736,33.5337437],[73.6103298,33.5117724],[73.6040033,33.4938069]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.6212449,33.4758396],[73.6314493,33.4486554],[73.595894,33.4380221],[73.5862409,33.4257503]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.6040033,33.4938069],[73.6212449,33.4758396]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.575941,33.9109027],[73.5939142,33.8872066],[73.5596079,33.7813447],[73.5985897,33.747968]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.5986353,33.7452151],[73.6067547,33.7259531],[73.6010127,33.7014501],[73.5733914,33.6752806]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.5342636,33.9691291],[73.5461669,33.9407682],[73.575941,33.9109027]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.5136885,34.0013148],[73.5342636,33.9691291]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.610535,34.570831],[73.641098,34.566941],[73.658034,34.595824],[73.674423,34.645828],[73.661102,34.684159],[73.67276,34.703324],[73.709153,34.732208],[73.720535,34.767213],[73.760269,34.799438],[73.799423,34.803604],[73.833879,34.826942],[73.89833,34.824716],[73.937194,34.849433],[73.986373,34.870544],[74.00444,34.905265],[74.05165,34.955552],[74.055816,34.996102],[74.085265,35.018326],[74.086655,35.029159],[74.062196,35.060547],[74.060256,35.074714],[74.126488,35.124629]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.3910239,34.376181],[73.398331,34.38234],[73.406937,34.424438],[73.432206,34.479158],[73.442749,34.554994],[73.464707,34.571937],[73.526093,34.57361],[73.581665,34.587768],[73.610535,34.570831]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.3910239,34.376181],[73.4029651,34.3530396],[73.4433592,34.3212751],[73.4594363,34.2883684],[73.4856852,34.2727067],[73.473291,34.260812],[73.4800625,34.233512],[73.4968165,34.2088658],[73.4903309,34.1830069],[73.4908513,34.1264472],[73.5004697,34.0946105]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.5136885,34.0013148],[73.4986167,34.0716161],[73.5004697,34.0946105]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[72.5107111,35.8960973],[72.5088281,35.9160773],[72.523606,35.926942],[72.566941,36.007507],[72.559402,36.033341],[72.521912,36.072228],[72.522475,36.106948],[72.544692,36.15499],[72.540253,36.202476],[72.567215,36.248298],[72.614151,36.267174],[72.663315,36.2594],[72.686357,36.273277],[72.7070145,36.27311],[72.735088,36.2990642],[72.7698226,36.3078837]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.8313682,32.9287784],[73.8924503,32.9246253],[73.9303692,32.933448],[73.9871521,32.9323311],[74.0097625,32.9426328],[74.0799088,32.9101082],[74.1349048,32.8991909],[74.183487,32.865696],[74.2369804,32.8475213],[74.253395,32.846275],[74.314689,32.8204231],[74.3275444,32.7829551],[74.3564689,32.7752404],[74.378952,32.801624],[74.397241,32.8009687],[74.408611,32.771667],[74.4638194,32.7766375]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.389758,32.918015],[79.40078,32.940565],[79.379557,32.945744],[79.334741,32.997603],[79.341835,33.014705],[79.328358,33.034464],[79.340579,33.057364],[79.359082,33.063018],[79.373109,33.118749],[79.400529,33.148002],[79.395886,33.169414]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.4148053,32.5422657],[79.46295,32.606116],[79.502924,32.643717]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.502924,32.643717],[79.524636,32.651546],[79.548375,32.676552],[79.540843,32.703226],[79.561282,32.714367],[79.543609,32.731088],[79.557271,32.759179]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.557271,32.759179],[79.5447607,32.7730679],[79.505323,32.783541],[79.4808,32.800677]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.4808,32.800677],[79.460914,32.833437],[79.470847,32.84268],[79.472568,32.876263],[79.446354,32.89098],[79.421025,32.895212],[79.389758,32.918015]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[75.9980541,36.484496],[76.0258665,36.4860462],[76.0692272,36.4775464],[76.1251036,36.4374592],[76.1731349,36.390302],[76.1986356,36.3874455],[76.2547611,36.3477937],[76.2973903,36.3504645],[76.3241353,36.361788],[76.352408,36.3598739],[76.4011526,36.3215115],[76.432004,36.308161],[76.4500366,36.2694054],[76.4324252,36.252071],[76.4560238,36.2413969],[76.4817068,36.2114093],[76.5448627,36.192757],[76.5629732,36.1730653],[76.6011647,36.1655489],[76.6372798,36.1849242],[76.6647957,36.1580606],[76.6972733,36.1358971],[76.7168607,36.0872442],[76.7260034,36.0784771],[76.8007285,36.0489926],[76.8049865,36.0168814],[76.7682293,36.0080173],[76.7543408,35.9826725],[76.7227707,35.9534305],[76.7856816,35.8899445],[76.8037794,35.8504117],[76.8653581,35.8264735],[76.8949429,35.7997362],[76.9352244,35.7840569],[77.004433,35.7750916],[77.033321,35.7764174],[77.081515,35.7977912],[77.1195362,35.796414],[77.1416322,35.7714596],[77.1658828,35.7586352],[77.1753165,35.7317285],[77.2221123,35.727116],[77.2245821,35.714978],[77.2854571,35.7110688],[77.3165673,35.7000154],[77.327599,35.7190901],[77.3584377,35.71772],[77.3832012,35.6931332],[77.4040978,35.6611799],[77.4269413,35.6593261],[77.4379117,35.6410899],[77.4271369,35.6105445],[77.4568505,35.5936084],[77.4655989,35.5733167],[77.4606132,35.5477076],[77.4836923,35.5363824],[77.4724145,35.5211137],[77.4402307,35.518059],[77.406699,35.5298165],[77.3783943,35.5503481],[77.3453946,35.5468226],[77.3227022,35.5367978]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[75.9526061,36.5372951],[75.9874928,36.502322],[75.9980541,36.484496]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[78.9054981,34.0051852],[78.9354793,34.0204138],[78.9952038,34.036832],[79.0189984,34.0161307],[79.0520729,34.0025678],[79.1087041,33.9901946],[79.1472513,33.9989986],[79.171046,34.0218414],[79.1938888,34.0249347],[79.2252977,34.010658],[79.2488543,34.0239829],[79.2969194,34.0094682],[79.3161931,34.014941],[79.3674407,33.9946664],[79.3906702,33.997095],[79.4101818,34.0101821],[79.4044711,34.0273142],[79.4294554,34.0596749],[79.4161304,34.1001257],[79.4382594,34.116544],[79.4701442,34.1160681],[79.4913214,34.134152],[79.497508,34.1741269],[79.4905886,34.1908996],[79.5586601,34.209343],[79.5898311,34.2409898],[79.6073639,34.2382215],[79.6100777,34.2841617],[79.6013549,34.3084887],[79.5732938,34.3214156],[79.5590171,34.3716222],[79.5347804,34.4007544],[79.5305915,34.4426436],[79.5091611,34.4511975]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.0851428,33.6195384],[79.0825254,33.6492817],[79.0557405,33.6653971],[79.0220871,33.674266],[79.0079801,33.6924626],[79.0096535,33.7097317],[79.0282783,33.7536762],[79.0004386,33.7734257],[78.9933002,33.7946029],[78.9652226,33.8212529],[78.9371449,33.9126242],[78.8871763,33.9725866],[78.9054981,34.0051852]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.395886,33.169414],[79.412751,33.176773],[79.38518,33.201913],[79.362314,33.19727],[79.33569,33.204029],[79.323878,33.190291],[79.255818,33.215399],[79.238447,33.233117],[79.216971,33.237886],[79.204401,33.20984],[79.182293,33.212241],[79.151971,33.181869]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[78.9366127,33.3869223],[78.9273846,33.440603],[78.9314297,33.4867645],[78.9097505,33.5654311],[78.8883614,33.572663],[78.9077147,33.621128]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[78.9077147,33.621128],[78.9499895,33.6176348],[79.0035273,33.6074032],[79.0251804,33.6190625],[79.0851428,33.6195384]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.5091611,34.4511975],[79.5381633,34.4788814],[79.5631331,34.457944],[79.604798,34.4653105],[79.6233578,34.449844],[79.6735645,34.454603],[79.6849859,34.4767319],[79.7033077,34.4862498],[79.7216296,34.4717351],[79.7526815,34.4776837],[79.7655306,34.4705453],[79.7967052,34.4822221]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.9079287,34.6946378],[79.9323937,34.6918877],[79.9515387,34.6737465],[79.9721896,34.6997035],[80.0152838,34.6936803],[80.0741464,34.7058061],[80.0792055,34.733439],[80.0669116,34.7622563],[80.0989921,34.7745363],[80.1204618,34.837705],[80.1674635,34.8800732],[80.1969966,34.8871385],[80.2060403,34.9114433],[80.1862573,34.9464874],[80.1975165,34.96062],[80.226635,34.9724685],[80.2335754,34.9895891],[80.2173653,35.0025938],[80.1936721,35.1184343]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.7967052,34.4822221],[79.8107404,34.5402635],[79.7882498,34.5640188],[79.8018136,34.5892643],[79.787018,34.605969],[79.7865473,34.6273539],[79.8297132,34.6460687],[79.8452731,34.6690141],[79.8984776,34.69619],[79.9079287,34.6946378]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[78.1571897,35.5509472],[78.1297226,35.5455394],[78.0766144,35.5599648],[78.0482686,35.5782463],[78.0224262,35.5642328],[77.9978907,35.5603415],[77.9672604,35.5681972],[77.9378503,35.559317],[77.9283455,35.5412573],[77.9338061,35.5224453],[77.9240974,35.4972106]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[78.2510871,35.7282772],[78.2401006,35.7062087],[78.2093646,35.6740077],[78.1817008,35.6673563],[78.1960618,35.6496696],[78.1617348,35.5929304],[78.1571897,35.5509472]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[78.8035393,35.8770789],[78.8002418,35.8698246],[78.7455044,35.8594926],[78.7217629,35.8630099],[78.6946747,35.8496793],[78.676115,35.8530106],[78.6566034,35.8220776],[78.6075865,35.8151771],[78.5767354,35.7972152],[78.5587882,35.7733295],[78.5225857,35.7627436],[78.5053525,35.771209],[78.462723,35.7644065],[78.4405012,35.7807327],[78.4211347,35.767396],[78.4087559,35.7306959],[78.3423159,35.7199],[78.3195253,35.7210563]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[78.3195253,35.7210563],[78.2510871,35.7282772]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[78.8558586,35.9742433],[78.836074,35.9419285],[78.8277205,35.9085145],[78.8035393,35.8770789]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.7042776,35.6390451],[79.7004922,35.6594497],[79.676768,35.6778252],[79.6786294,35.7002396],[79.6645914,35.7185434],[79.6451242,35.7238949],[79.6424872,35.7501872],[79.6295668,35.7782117],[79.5925417,35.7888788],[79.56427,35.8297913],[79.5883037,35.877887],[79.5664131,35.8982187]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[80.3664698,35.5251075],[80.3294639,35.534929],[80.3021734,35.5340292],[80.2974544,35.5560939],[80.2238609,35.5550776],[80.2054132,35.5751497],[80.1713024,35.557166],[80.1659653,35.5466079],[80.1825866,35.5088458],[80.1419485,35.4979942],[80.1271319,35.4783793],[80.1099998,35.4783793],[80.0884656,35.4381664],[80.0722853,35.4231758]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[80.1936721,35.1184343],[80.2374457,35.1543721],[80.2223395,35.1780178],[80.2468013,35.1856643],[80.2563285,35.2041814],[80.2852472,35.211262]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[80.2852472,35.211262],[80.2942892,35.2678932]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[80.0722853,35.4231758],[80.0349278,35.4386423],[80.0168439,35.4657682],[80.0068501,35.4962253],[79.994001,35.5097882],[79.9645318,35.5714719],[80.0010814,35.5924701],[79.9545853,35.6162313],[79.957585,35.6328089]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.957585,35.6328089],[79.9245878,35.620652],[79.9209565,35.604706],[79.8675304,35.5974051],[79.8607658,35.5766603],[79.8367138,35.5862811],[79.8392693,35.6016142],[79.816842,35.6269739],[79.7714728,35.6256662],[79.7340147,35.642769],[79.7042776,35.6390451]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[80.2925906,35.3604184],[80.3467536,35.3910228],[80.3769176,35.3923438],[80.4119255,35.4260306],[80.4208426,35.4430942]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[80.2942892,35.2678932],[80.2662115,35.2964467],[80.2925906,35.3604184]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[80.4208426,35.4430942],[80.4115952,35.4771112],[80.3894676,35.4876797],[80.3660968,35.5134914],[80.3664698,35.5251075]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.5664131,35.8982187],[79.5578387,35.8803554],[79.5253079,35.8657997],[79.5013308,35.8465243],[79.4717125,35.8586372],[79.4522501,35.8827864],[79.468368,35.9003393],[79.4563342,35.9240926],[79.4396228,35.9341975],[79.4369169,35.9722297],[79.3920214,35.9762218],[79.3434392,35.9878727],[79.3383831,35.9634717],[79.3131028,35.9568768],[79.2695767,35.9274197]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[79.2695767,35.9274197],[79.1862615,35.9331353],[79.1644984,35.9447862],[79.077666,35.9469845],[79.0572219,35.9414888],[79.0257863,35.9151093],[79.0114975,35.9311568],[78.9635747,35.9370922],[78.9547816,35.9485233],[78.9264237,35.9452259],[78.8558586,35.9742433]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.841253,36.925945],[73.827306,36.899294],[73.799916,36.890602],[73.7521,36.912051],[73.734868,36.903018],[73.69158,36.915797],[73.654106,36.908791]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.9999254,36.9979211],[74.9189999,36.978957],[74.931881,36.945459],[74.9060629,36.92974],[74.8837271,36.936386],[74.8801829,36.960527],[74.8402621,37.019909],[74.836669,37.058713],[74.805633,37.053186],[74.794419,37.031096],[74.7741229,37.022023],[74.739209,37.021655],[74.717482,37.037366],[74.7214841,37.05441],[74.7019699,37.082359],[74.667951,37.082416],[74.622406,37.059823],[74.624373,37.047285],[74.5801222,37.030568],[74.564626,37.030852]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.065299,36.841091],[74.048114,36.826411],[74.003385,36.836827],[73.99436,36.830229],[73.95743,36.842518],[73.932234,36.87474],[73.914651,36.869367],[73.892046,36.879803]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[73.892046,36.879803],[73.90677,36.900804],[73.890465,36.912521],[73.841253,36.925945]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.153365,36.898165],[74.129893,36.8637],[74.12079,36.83822],[74.065299,36.841091]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.315913,36.938305],[74.309291,36.918906],[74.237211,36.894872],[74.223558,36.896831]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.223558,36.896831],[74.180747,36.916499],[74.153365,36.898165]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.534055,36.957221],[74.506271,36.966017],[74.491484,36.988002],[74.426558,37.01115]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.395311,36.973441],[74.388036,36.964049],[74.350169,36.962302],[74.315913,36.938305]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.426558,37.01115],[74.413732,36.975273],[74.395311,36.973441]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[74.564626,37.030852],[74.51893,37.017367],[74.543524,36.967085],[74.534055,36.957221]]},"properties":{"source":"osm"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[75.19221,36.974688],[75.2162911,36.971826],[75.233421,36.959055],[75.285346,36.974495],[75.315789,36.969826],[75.327187,36.958537],[75.3589271,36.956816],[75.3753049,36.946841],[75.3995239,36.955057],[75.4058632,36.936584],[75.387195,36.925916],[75.385606,36.903406],[75.4202048,36.889483],[75.4318285,36.8301725],[75.419898,36.7994229],[75.4195976,36.7733706],[75.4435014,36.7467937],[75.4514837,36.7246105],[75.4694223,36.7159075],[75.4992641,36.739151],[75.5324539,36.721379],[75.5339051,36.771441],[75.5736412,36.766664],[75.6373748,36.770191],[75.673418,36.764956],[75.6981101,36.74689],[75.7195099,36.752095],[75.7658138,36.724879]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[75.19221,36.974688],[75.1581259,36.992952],[75.168135,37.009677],[75.1376891,37.026454],[75.1254728,37.012544],[75.0480739,37.00475],[75.0292459,37.01624],[74.9999254,36.9979211]]},"properties":{"source":"hand"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[75.7658138,36.724879],[75.803792,36.710235],[75.8487161,36.673665],[75.864847,36.669735],[75.888166,36.639054],[75.911314,36.629027],[75.9406328,36.5928932],[75.9257841,36.5730437],[75.9526061,36.5372951]]},"properties":{"source":"hand"}}]}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INDIA_BOUNDARY_ATTRIBUTION_TEXT = exports.INDIA_BOUNDARY_ATTRIBUTION = void 0;
4
+ /**
5
+ * The attribution the OSM-corroborated portion of this dataset requires.
6
+ *
7
+ * The OSM-derived geometry is ODbL 1.0, which asks for attribution wherever the
8
+ * data is shown — so the layer factories in this package set this string as the
9
+ * layer's `attribution` by default and Leaflet's attribution control picks it up
10
+ * on its own. That is deliberate: an adopter should be compliant without having
11
+ * read the licence section first.
12
+ *
13
+ * If your map already credits OpenStreetMap for its tiles, this is still worth
14
+ * keeping. The tile credit covers the basemap; this covers the overlay, which is
15
+ * a separate extract of the same database.
16
+ *
17
+ * Note what it does *not* say. It does not claim OSM as the source of the whole
18
+ * line — 31 of the 67 features have no OSM counterpart (see `data.ts`) — and it
19
+ * does not suggest OpenStreetMap or its contributors endorse the depiction.
20
+ */
21
+ exports.INDIA_BOUNDARY_ATTRIBUTION = 'India boundary: <a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</a> (ODbL), partly hand-carried';
22
+ /** The same credit without markup, for renderers whose attribution is plain text. */
23
+ exports.INDIA_BOUNDARY_ATTRIBUTION_TEXT = "India boundary: © OpenStreetMap contributors (ODbL), partly hand-carried";
24
+ //# sourceMappingURL=attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.js","sourceRoot":"","sources":["../../src/attribution.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;GAgBG;AACU,QAAA,0BAA0B,GACrC,gIAAgI,CAAC;AAEnI,qFAAqF;AACxE,QAAA,+BAA+B,GAC1C,0EAA0E,CAAC"}