All projects
Active DevelopmentOct 2026 — present

bharatmap

Correct India borders on web maps: MapLibre, Leaflet, OpenLayers, deck.gl.

A small zero-dependency JavaScript module that fixes how free vector basemaps draw India's borders. It rewrites each vector tile as it loads, cutting the disputed Kashmir, Ladakh and Arunachal Pradesh lines that OpenStreetMap-based styles draw and drawing the Survey of India boundary in their place, in the basemap's own line style. It ships as an npm package with TypeScript types, a tile proxy for clients that cannot hook tile loading, a Claude Code plugin and a skill file so AI assistants can apply the fix to a project.

Built with

JavaScriptTypeScriptMapLibre GL JSVector tilesReactVite

Highlights

  • ▸Cuts the disputed border geometry inside each vector tile as it loads, which a style filter cannot do because the tile builder merges several borders into one line.
  • ▸One function call for MapLibre, a style fix for Leaflet and OpenLayers, and a tile proxy for Mapbox GL JS, Tangram and QGIS. No server or API key needed.
  • ▸About 40 kB gzipped with no runtime dependencies. Code is Apache-2.0, border data is ODbL.
  • ▸The docs site is built with Cloudflare's Kumo UI and prerendered to static HTML, with a live before and after map slider in light and dark.

From the README

bharatmap

bharatmap: correct India map borders for web maps

npm version npm downloads per month GitHub stars Bundle size Types included License

bharatmap: correct India borders on web maps

Site and live demo: https://bharatmap.vkrishna04.me/. Made by VKrishna04.

bharatmap makes a web map draw India's boundary the way the Survey of India does: Jammu and Kashmir, Ladakh (with Gilgit-Baltistan and Aksai Chin) and Arunachal Pradesh inside India, with no stray Line of Actual Control or Line of Control fragments, at every zoom level. It works with MapLibre GL JS, Leaflet (vector basemaps), OpenLayers and deck.gl, and a tile proxy covers other vector-tile libraries.

npm install bharatmap

No runtime dependencies, about 40 kB gzipped, Apache-2.0.

Why your map shows India wrongly

Free basemaps (CARTO, OpenFreeMap, MapTiler, Stadia and others) are built from OpenStreetMap, whose contributors draw disputed borders as the lines on the ground. So an OSM-based map shows the LAC and LoC and the China side of Arunachal Pradesh as international borders, often as dashed fragments that are still visible at world zoom. A style filter cannot fix this: the tile builder merges several disputed borders into one long line, so a filter removes all of it or none of it, and a box big enough to catch the strays also deletes real borders (Myanmar, Bhutan) in Arunachal Pradesh.

bharatmap cuts the geometry inside each vector tile as it loads, then draws India's boundary in its place, styled to match the other borders of the basemap. No server, no API key, no change of tile provider.

MapLibre GL JS (one call)

import maplibregl from "maplibre-gl";
import { fixMap } from "bharatmap";

const map = new maplibregl.Map({ container: "map", style: STYLE_URL, center: [78, 22], zoom: 4 });
await fixMap(maplibregl, map);   // fixes this style and every later map.setStyle (theme switches too)

Or fix the style before the map exists:

import { install, fixStyle } from "bharatmap";
install(maplibregl);
const map = new maplibregl.Map({ container: "map", style: await fixStyle(STYLE_URL) });

From a script tag, no bundler:

<script src="https://cdn.jsdelivr.net/npm/bharatmap@0.2.0/dist/bharatmap.iife.js"></script>
<script>
  BharatMap.install(maplibregl);
  BharatMap.fixStyle("https://basemaps.cartocdn.com/gl/positron-gl-style/style.json").then((style) => {
    new maplibregl.Map({ container: "map", style, center: [78, 22], zoom: 4 });
  });
</script>

Which libraries work

LibraryHowChecked
MapLibre GL JSfixMap(maplibregl, map) or fixStylein a browser, light and dark, zoom 2 to 14
Leaflet with a vector basemap (maplibre-gl-leaflet)fixStyle, then L.maplibreGL({ style })in a browser, examples/leaflet.html
OpenLayers with ol-mapbox-stylefixStyle(url, { protocol: false }) and olTileLoadFunctionin a browser, examples/openlayers.html
deck.gl, react-map-gl, Vue and Angular wrappers on MapLibresame as MapLibre, on the map or style you pass insame engine, not tested separately
Mapbox GL JS, Tangram, QGIS, any other vector-tile clienttileProxy, a fetch handler you host (examples/cloudflare-worker.js)unit tests only
Leaflet with raster tilesoverlay only: L.geoJSON(BharatMap.boundary)the raster's own line stays underneath
Google Mapsnot neededGoogle already draws India's borders per local depiction on its own
Apple Mapsnot possiblethe vendor decides what it draws

A raster tile is a picture, so a border baked into it cannot be cut. Use a vector basemap.

Mapbox's own styles have a worldview setting that is meant for this, so on Mapbox basemaps check their documentation first. bharatmap is for the open basemaps that have no such setting.

Leaflet

import maplibregl from "maplibre-gl";
import { install, fixStyle } from "bharatmap";
install(maplibregl);
const style = await fixStyle("https://basemaps.cartocdn.com/gl/positron-gl-style/style.json");
L.maplibreGL({ style }).addTo(map);

OpenLayers

import { fixStyle, olTileLoadFunction } from "bharatmap";
import { apply } from "ol-mapbox-style";
import VectorTileSource from "ol/source/VectorTile";

await apply(map, await fixStyle(STYLE_URL, { protocol: false }));
map.getAllLayers().forEach((layer) => {
  const source = layer.getSource && layer.getSource();
  if (source instanceof VectorTileSource) { source.setTileLoadFunction(olTileLoadFunction); source.refresh(); }
});

Any other vector-tile client

Host the corrected tiles and point the client at them. tileProxy(upstream) returns a fetch handler for Cloudflare Workers, Deno, Bun or Node 18 and later that serves /{z}/{x}/{y}.pbf. The replacement lines are in boundary, a GeoJSON FeatureCollection you can add to any map.

Options

fixStyle(style, opts) takes a style URL or object and returns a new style. It does not change the one you pass in.

optionmeaning
skip(layer)return true for a country-line layer that should not get a replacement copy, for example a wide soft outline you hide anyway
dataa GeoJSON FeatureCollection of lines to draw in place of the built-in ones
protocolfalse leaves tile URLs alone (OpenLayers)

TypeScript types are included.

Which basemaps work

Any vector basemap that follows the OpenMapTiles schema, which has a boundary layer with admin_level 2 for countries and a disputed flag: CARTO (Positron, Dark Matter, Voyager), OpenFreeMap, MapTiler, Stadia and others. Other schemas need a profile, see docs/basemaps.md.

Questions

Why does my map show Kashmir or Arunachal Pradesh wrongly? The basemap is built from OpenStreetMap, which does not follow India's official depiction. See above.

Does it work at every zoom? Yes, because it edits the tile geometry rather than filtering. The coverage check reports zero gaps from zoom 1 to 10 and a few tile-edge pieces at zoom 11 and 12. Zoom 13 and above was checked by eye. See docs/how-it-works.md.

Does it slow the map down? Tiles that do not touch the affected areas are returned untouched. The others take a few milliseconds each in the browser.

Does it need a server or an API key? No. Only the optional tile proxy needs hosting.

Is the line legally authoritative? No. It follows a published depiction of the Survey of India line, checked against OpenStreetMap where OSM independently draws the same line. For publication, verify it against the Survey of India itself.

Can an AI assistant add it to my project? Yes. skills/bharatmap/SKILL.md is a skill for Claude Code and similar tools, and prompts/integrate.md is the same instructions as a prompt for any assistant. llms.txt is a short summary for crawlers.

Let your AI assistant do it

One command puts the instructions where your assistant looks (Claude Code, Cursor, Copilot, Windsurf, Gemini CLI, or AGENTS.md for the rest):

npx bharatmap init

Then ask: "fix the India borders on my map with bharatmap". Claude Code users can also install it as a plugin:

/plugin marketplace add Life-Experimentalist/bharatmap
/plugin install bharatmap@bharatmap

Any assistant that can read a URL can be told: "follow https://bharatmap.vkrishna04.me/skill.md". That file is always the current version.

Check it yourself

npm install
npm test
npm run verify:coverage          # fetches real CARTO tiles for zoom 1 to 10 and looks for gaps

Limits

  • The replacement lines are a depiction, short stretches are hand-carried. See the question above.
  • Printing, static map images and server-side rendering are not covered, only live web maps.
  • Mapbox GL JS cannot intercept tile requests the way MapLibre can, so it needs the tile proxy.

License

Code: Apache-2.0. Border data: ODbL 1.0, see DATA-LICENSE.md and NOTICE. Attribution for the data is added to the map's credits automatically.

Local Storage Notice

This site uses local storage to cache GitHub data for better performance, and an anonymous visit counter. No personal data is stored or shared.