mirror of
https://github.com/opengeos/GeoLibre.git
synced 2026-11-04 10:43:36 +00:00
feat(plugins): add India GIS open geospatial and state geoportal catalog plugin (#3274)
* feat(plugins): add India GIS open geospatial and state geoportal catalog plugin * feat(desktop): register India GIS plugin in built-in plugins list, UI profiles, and i18n locales * style: auto-format (ruff + oxfmt) [pre-commit.ci] * feat(plugins): provide authoritative Indian government geospatial datasets and documentation for India GIS * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address review feedback - Replace the geoBoundaries raw.githubusercontent.com URLs, which serve Git LFS pointer text, with media.githubusercontent.com URLs that serve the GeoJSON with CORS. - Load only a state's districts on each state district card: items take a featureFilter, and the cards keep the GADM NAME_1 values of their states. - Name each dataset's real publisher (DataMeet, GADM, geoBoundaries) and drop the "authoritative/official" wording; regroup the national boundaries under their publishers and drop the Bhuvan entry that repeated a file. - Credit DataMeet and link the source file on every Municipal Spatial Data card, as its license asks. - Drop the Kanpur wards (Web Mercator metres) and four files that are not valid GeoJSON (Kochi wards and boundary, Ahmedabad zonal and ward offices). - Correct feature counts and extents from the files themselves. - Make check:gis-portals download every India file and check that it is a FeatureCollection in longitude/latitude. - Remove the unused orgId placeholder and the searchQuery and itemFilter plumbing, which no India catalog used; this also restores the guard that never searches ArcGIS Online without an org or group scope. - Pick a map service's first "Feature Layer" rather than its first non-group layer, and type subLayerIds as nullable. - Translate the India panel through usePluginLabelSync and indiaGis.* keys. * Address CodeRabbit review feedback - Check every position of every retained feature for longitude/latitude in check:gis-portals, not only the first feature's first position. --------- Co-authored-by: Prince Suthar <sutharprince098@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <giswqs@gmail.com>
This commit is contained in:
18 files changed
+1424
-22
No files matched your search
@@ -12,6 +12,7 @@ import {
|
||||
setGraticuleLabels,
|
||||
setH3Labels,
|
||||
setHuggingFaceLabels,
|
||||
setIndiaGisLabels,
|
||||
setMapillaryLabels,
|
||||
setOlcLabels,
|
||||
setOpenAerialMapLabels,
|
||||
@@ -269,6 +270,17 @@ export function usePluginLabelSync(): void {
|
||||
chooseCatalogSet: t("usLocalGis.chooseStateHint"),
|
||||
openPortal: t("usLocalGis.openPortal"),
|
||||
});
|
||||
setIndiaGisLabels({
|
||||
...arcGisHubLabels,
|
||||
hint: t("indiaGis.hint"),
|
||||
searchPlaceholder: t("indiaGis.searchPlaceholder"),
|
||||
noResults: t("indiaGis.noResults"),
|
||||
searchError: t("indiaGis.searchError"),
|
||||
catalogSet: t("indiaGis.chooseRegion"),
|
||||
catalog: t("indiaGis.portal"),
|
||||
chooseCatalogSet: t("indiaGis.chooseRegionHint"),
|
||||
openPortal: t("indiaGis.openPortal"),
|
||||
});
|
||||
setUsFederalGisLabels({
|
||||
...arcGisHubLabels,
|
||||
hint: t("usFederalGis.hint"),
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
maplibreUsFederalGisPlugin,
|
||||
maplibreUsStateGisPlugin,
|
||||
maplibreUsLocalGisPlugin,
|
||||
maplibreIndiaGisPlugin,
|
||||
maplibreCkanPlugin,
|
||||
maplibreSocrataPlugin,
|
||||
maplibreStacCatalogsPlugin,
|
||||
@@ -237,6 +238,7 @@ const BUILT_IN_PLUGINS: GeoLibrePlugin[] = [
|
||||
maplibreUsFederalGisPlugin,
|
||||
maplibreUsStateGisPlugin,
|
||||
maplibreUsLocalGisPlugin,
|
||||
maplibreIndiaGisPlugin,
|
||||
maplibreSocrataPlugin,
|
||||
maplibreCkanPlugin,
|
||||
maplibreStacCatalogsPlugin,
|
||||
|
||||
@@ -4238,6 +4238,7 @@
|
||||
"geolibre-tennessee-gis": "Tennessee GIS",
|
||||
"geolibre-us-state-gis": "US state GIS",
|
||||
"geolibre-us-local-gis": "US local GIS",
|
||||
"geolibre-india-gis": "India GIS",
|
||||
"geolibre-us-federal-gis": "US federal GIS",
|
||||
"geolibre-osm-downloader": "OSM downloader",
|
||||
"geolibre-ign-lidar-hd": "IGN LiDAR HD",
|
||||
@@ -4999,6 +5000,16 @@
|
||||
"chooseStateHint": "Choose a state, then a city or county, to list its GIS data.",
|
||||
"openPortal": "Open portal"
|
||||
},
|
||||
"indiaGis": {
|
||||
"hint": "Choose a region, then a portal, to browse open GIS data for India. Add layers to the map or download data.",
|
||||
"searchPlaceholder": "Search India GIS datasets",
|
||||
"noResults": "No matching datasets found for this portal.",
|
||||
"searchError": "Could not list this portal's datasets.",
|
||||
"chooseRegion": "Choose a region",
|
||||
"portal": "Portal",
|
||||
"chooseRegionHint": "Choose a region, then a portal, to list its GIS data.",
|
||||
"openPortal": "Open portal"
|
||||
},
|
||||
"usFederalGis": {
|
||||
"hint": "Choose a department, then an agency, to browse and search its public GIS data. Add layers to the map or download data.",
|
||||
"searchPlaceholder": "Search the agency's GIS data",
|
||||
|
||||
@@ -4233,6 +4233,7 @@
|
||||
"geolibre-tennessee-gis": "Tennessee GIS",
|
||||
"geolibre-us-state-gis": "अमेरिकी राज्य GIS",
|
||||
"geolibre-us-local-gis": "अमेरिकी स्थानीय GIS",
|
||||
"geolibre-india-gis": "भारत GIS",
|
||||
"geolibre-us-federal-gis": "अमेरिकी संघीय GIS",
|
||||
"geolibre-osm-downloader": "OSM डाउनलोडर",
|
||||
"geolibre-ign-lidar-hd": "IGN LiDAR HD",
|
||||
|
||||
@@ -240,6 +240,7 @@ export const PLUGIN_TIERS: Record<string, ComplexityTier> = {
|
||||
"geolibre-us-federal-gis": "advanced",
|
||||
"geolibre-us-state-gis": "advanced",
|
||||
"geolibre-us-local-gis": "advanced",
|
||||
"geolibre-india-gis": "advanced",
|
||||
"maplibre-gl-source-coop": "advanced",
|
||||
"geolibre-s3-browser": "advanced",
|
||||
"maplibre-gl-huggingface": "advanced",
|
||||
|
||||
@@ -28,7 +28,7 @@ plugins a link cannot open.
|
||||
<!-- Generated by scripts/gen-plugin-reference.mjs from the plugin registry and
|
||||
scripts/plugin-reference.json. Do not edit by hand: run `npm run plugins:docs`. -->
|
||||
|
||||
GeoLibre registers 70 built-in plugins.
|
||||
GeoLibre registers 71 built-in plugins.
|
||||
|
||||
| Plugin | Where | Link name | What it adds |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -57,6 +57,7 @@ GeoLibre registers 70 built-in plugins.
|
||||
| **US federal GIS** | Plugins → Web services | `us-federal-gis` | The public GIS portals of 24 US federal agencies. See [US federal GIS](web-services.md#us-federal-gis). |
|
||||
| **US state GIS** | Plugins → Web services | `us-state-gis` | The public GIS data portals of all 50 states and DC. See [US state GIS](web-services.md#us-state-gis). |
|
||||
| **US local GIS** | Plugins → Web services | `us-local-gis` | The GIS and open-data portals of about 150 large US cities, counties, and regional agencies. See [US local GIS](web-services.md#us-local-gis). |
|
||||
| **India GIS** | Plugins → Web services | `india-gis` | Open national, state, and city boundary datasets for India. See [India GIS](web-services.md#india-gis). |
|
||||
| **Socrata** | Plugins → Web services | `socrata` | Search government Socrata open-data portals. See [Socrata](web-services.md#socrata). |
|
||||
| **CKAN** | Plugins → Web services | `ckan` | Humanitarian Data Exchange (CKAN) resources. See [CKAN](web-services.md#ckan). |
|
||||
| **STAC catalogs** | Plugins → Web services | `stac-catalogs` | Any STAC API or static catalog, via STAC Index. See [STAC catalogs](web-services.md#stac-catalogs). |
|
||||
|
||||
@@ -38,6 +38,7 @@ They are grouped together because they behave the same way, not because they sha
|
||||
| [US Federal GIS](#us-federal-gis) | US federal agencies | The public GIS portals of 24 federal agencies, from the Census Bureau and NOAA to USGS and FEMA |
|
||||
| [US State GIS](#us-state-gis) | US state GIS offices | The public GIS data portals of all 50 states and DC |
|
||||
| [US Local GIS](#us-local-gis) | US cities and counties | The GIS and open-data portals of about 150 large US cities, counties, and regional agencies |
|
||||
| [India GIS](#india-gis) | DataMeet, GADM, geoBoundaries | Open national, state, and city boundary datasets for India |
|
||||
| [Socrata](#socrata) | Socrata | Government open-data portals |
|
||||
| [CKAN](#ckan) | HDX | Humanitarian Data Exchange resources |
|
||||
| [STAC Catalogs](#stac-catalogs) | any STAC | Any STAC API or static catalog, via STAC Index |
|
||||
@@ -264,6 +265,15 @@ Browses the public GIS and open-data portals of large US cities and counties, pl
|
||||
- The rest are [Socrata](#socrata) portals, which many of the largest cities use (New York, Chicago, San Francisco, Seattle, Austin, Dallas). There the panel lists only datasets with a geometry column and adds each one as GeoJSON, zooming to what was loaded. A Socrata export is capped at 50,000 features, so a city-wide incident log arrives as its first 50,000 rows; filter it on the portal first for a complete subset. Socrata cannot search by extent, so **Search the current map area** is unavailable for those portals, and the result count is the number found so far rather than a total.
|
||||
- Portals on other platforms (CKAN, OpenDataSoft, or custom sites such as Boston's or Pittsburgh's main portals) cannot be searched and are not listed, though their ArcGIS Hub sites are where one exists. Every listed portal was checked to return datasets in September 2026; `npm run check:gis-portals` repeats the check.
|
||||
|
||||
## India GIS
|
||||
|
||||
Lists open boundary and municipal datasets for India, grouped by region: national boundaries, then state and city portals (Delhi, Bengaluru, Chennai, Hyderabad, Mumbai, Ahmedabad, Chandigarh, and others).
|
||||
|
||||
- Choose a region, then a portal; the panel lists its datasets immediately. Type a keyword to filter them by title and description. **Search the current map area** does not apply.
|
||||
- No Indian government portal offers a catalog the panel can search, so each portal is a fixed list of GeoJSON files, and every card names where its file comes from: [DataMeet](https://datameet.org/) (the national outline and city wards), [GADM](https://gadm.org/) (states, districts, and sub-districts, with older boundaries), or [geoBoundaries](https://www.geoboundaries.org/). **Open portal** opens the government agency's own site.
|
||||
- A state's district card loads only that state's districts from the all-India GADM file; **Download** saves the whole file.
|
||||
- `npm run check:gis-portals -- india` downloads every file and checks that it is GeoJSON in longitude and latitude.
|
||||
|
||||
## Socrata
|
||||
|
||||
Searches public [Socrata](https://dev.socrata.com/) open-data catalogs — the platform behind many city, county, and state data portals — and adds their GeoJSON datasets to the map. Keyword search, paged results, and **Load more**, the same as ArcGIS Hub.
|
||||
|
||||
@@ -766,6 +766,13 @@ export {
|
||||
US_LOCAL_GIS_PLUGIN_ID,
|
||||
} from "./plugins/maplibre-us-local-gis";
|
||||
export { US_LOCAL_GIS_CATALOGS } from "./plugins/us-local-gis-catalogs";
|
||||
export {
|
||||
DEFAULT_INDIA_GIS_LABELS,
|
||||
maplibreIndiaGisPlugin,
|
||||
setIndiaGisLabels,
|
||||
INDIA_GIS_PLUGIN_ID,
|
||||
} from "./plugins/maplibre-india-gis";
|
||||
export { INDIA_GIS_CATALOGS } from "./plugins/india-gis-catalogs";
|
||||
export {
|
||||
DEFAULT_US_FEDERAL_GIS_LABELS,
|
||||
maplibreUsFederalGisPlugin,
|
||||
|
||||
@@ -22,6 +22,12 @@ export interface ArcGisHubItem {
|
||||
pageUrl?: string;
|
||||
/** The most features `dataUrl` returns, when the export is capped. */
|
||||
featureLimit?: number;
|
||||
/**
|
||||
* Keep only the features of `dataUrl` whose `property` is one of `values`,
|
||||
* for a card that shows part of a larger GeoJSON file (one state's districts
|
||||
* from a national file).
|
||||
*/
|
||||
featureFilter?: { property: string; values: readonly string[] };
|
||||
}
|
||||
|
||||
export interface ArcGisHubSearchResult {
|
||||
|
||||
@@ -245,7 +245,10 @@ interface ArcGISFeatureLayerInfo extends ArcGISEditInfo {
|
||||
interface ArcGISFeatureServiceInfo {
|
||||
layers?: Array<{
|
||||
id: number;
|
||||
subLayerIds?: number[];
|
||||
/** A map service names each layer's kind ("Feature Layer", "Raster Layer"...). */
|
||||
type?: string;
|
||||
/** Null, not absent, on a map service's non-group layers. */
|
||||
subLayerIds?: number[] | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -2575,11 +2578,32 @@ async function resolvePortalFeatureLayerUrl(
|
||||
{ cause },
|
||||
);
|
||||
}
|
||||
// City and county ArcGIS Server sites often register a map service layer as
|
||||
// a "Feature Service" item. Such a layer answers the same `/query` requests
|
||||
// as a FeatureServer layer; the caller's geometry-type check still rejects
|
||||
// one that is not a feature layer.
|
||||
// City and county ArcGIS Server sites often register a map service or map
|
||||
// service layer as a "Feature Service" item. Such a layer answers the same
|
||||
// `/query` requests as a FeatureServer layer; the caller's geometry-type
|
||||
// check still rejects one that is not a feature layer.
|
||||
if (/\/MapServer\/\d+\/?$/i.test(itemInfo.url)) return trimTrailingSlash(itemInfo.url);
|
||||
if (/\/MapServer\/?$/i.test(itemInfo.url)) {
|
||||
const serviceInfo = await fetchArcGISJson<ArcGISFeatureServiceInfo>(
|
||||
itemInfo.url,
|
||||
options,
|
||||
cause,
|
||||
);
|
||||
// A map service can list raster or annotation layers before its first
|
||||
// feature layer; older servers omit `type`, so fall back to any non-group.
|
||||
const layerId = serviceInfo.layers?.find((layer) =>
|
||||
layer.type ? layer.type === "Feature Layer" : !layer.subLayerIds?.length,
|
||||
)?.id;
|
||||
if (layerId == null) {
|
||||
throw new LocalizedError(
|
||||
`${ERROR_KEY}.noFeatureLayer`,
|
||||
"The ArcGIS feature service does not list a feature layer.",
|
||||
undefined,
|
||||
{ cause },
|
||||
);
|
||||
}
|
||||
return `${trimTrailingSlash(itemInfo.url)}/${layerId}`;
|
||||
}
|
||||
return resolveFeatureLayerUrl(itemInfo.url, options, cause);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,953 @@
|
||||
import type { ArcGisHubItem } from "./arcgis-hub-api";
|
||||
import type { ArcGisHubCatalog, ArcGisHubCatalogSet } from "./maplibre-arcgis-hub";
|
||||
|
||||
type PortalEntry = Omit<ArcGisHubCatalog, "id">;
|
||||
type Extent = [[number, number], [number, number]];
|
||||
|
||||
const DATAMEET_MUNICIPAL =
|
||||
"https://raw.githubusercontent.com/datameet/Municipal_Spatial_Data/master/";
|
||||
const GADM_INDIA_DISTRICTS =
|
||||
"https://raw.githubusercontent.com/geohacker/india/master/district/india_district.geojson";
|
||||
// media.githubusercontent.com serves Git LFS files (raw.githubusercontent.com
|
||||
// returns the LFS pointer) and, unlike github.com's redirect, allows CORS.
|
||||
const GEOBOUNDARIES_INDIA =
|
||||
"https://media.githubusercontent.com/media/wmgeolab/geoBoundaries/9469f09592ced973a3448cf66b6100b741b64c0d/releaseData/gbOpen/IND/";
|
||||
|
||||
function geoJsonItem(
|
||||
id: string,
|
||||
title: string,
|
||||
owner: string,
|
||||
dataUrl: string,
|
||||
pageUrl: string,
|
||||
snippet: string,
|
||||
extent: Extent,
|
||||
): ArcGisHubItem {
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
owner,
|
||||
type: "GeoJson",
|
||||
snippet,
|
||||
description: snippet,
|
||||
dataUrl,
|
||||
pageUrl,
|
||||
extent,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a card for a file of DataMeet's Municipal Spatial Data, a community
|
||||
* repository of city ward and boundary maps. Its license asks that every
|
||||
* dataset credit DataMeet and link to the file, so the card does both.
|
||||
*
|
||||
* Args:
|
||||
* id: The card's unique id.
|
||||
* title: The card's title.
|
||||
* path: The file's path in the repository (`<City>/<file>.geojson`).
|
||||
* snippet: What the file maps.
|
||||
* extent: The file's bounds.
|
||||
*
|
||||
* Returns:
|
||||
* The catalog item.
|
||||
*/
|
||||
function datameetItem(
|
||||
id: string,
|
||||
title: string,
|
||||
path: string,
|
||||
snippet: string,
|
||||
extent: Extent,
|
||||
): ArcGisHubItem {
|
||||
return geoJsonItem(
|
||||
id,
|
||||
title,
|
||||
"DataMeet community",
|
||||
`${DATAMEET_MUNICIPAL}${path}`,
|
||||
`https://github.com/datameet/Municipal_Spatial_Data/blob/master/${path}`,
|
||||
`${snippet} Community dataset from DataMeet's Municipal Spatial Data.`,
|
||||
extent,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a card for some states' districts, cut from the all-India GADM
|
||||
* district file by its `NAME_1` (state) property when the card is added.
|
||||
*
|
||||
* Args:
|
||||
* id: The card's unique id.
|
||||
* title: The card's title.
|
||||
* states: The `NAME_1` values to keep (GADM's spelling, such as "Orissa").
|
||||
* snippet: What the card maps.
|
||||
* extent: The kept districts' bounds.
|
||||
*
|
||||
* Returns:
|
||||
* The catalog item.
|
||||
*/
|
||||
function gadmDistricts(
|
||||
id: string,
|
||||
title: string,
|
||||
states: readonly string[],
|
||||
snippet: string,
|
||||
extent: Extent,
|
||||
): ArcGisHubItem {
|
||||
return {
|
||||
...geoJsonItem(
|
||||
id,
|
||||
title,
|
||||
"GADM (via geohacker/india)",
|
||||
GADM_INDIA_DISTRICTS,
|
||||
"https://github.com/geohacker/india",
|
||||
snippet,
|
||||
extent,
|
||||
),
|
||||
featureFilter: { property: "NAME_1", values: states },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build one Indian region/state catalog set.
|
||||
*
|
||||
* Args:
|
||||
* code: The region's short code (e.g., 'national', 'dl', 'ka', 'mh').
|
||||
* name: The region's display name.
|
||||
* portals: Its open data and GIS portals.
|
||||
*
|
||||
* Returns:
|
||||
* The region's catalog set.
|
||||
*/
|
||||
function region(code: string, name: string, portals: PortalEntry[]): ArcGisHubCatalogSet {
|
||||
return {
|
||||
id: code,
|
||||
name,
|
||||
catalogs: portals.map((portal) => ({
|
||||
id: `${code}-${portal.name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-|-$/g, "")}`,
|
||||
...portal,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Open GIS data for India, grouped by region. The national boundaries link to
|
||||
* their publishers; a state or city portal links to that government's own
|
||||
* site, while its datasets come from the community or global sources each
|
||||
* card names (no Indian government portal here offers a searchable catalog).
|
||||
*/
|
||||
export const INDIA_GIS_CATALOGS: readonly ArcGisHubCatalogSet[] = [
|
||||
region("national", "National boundaries", [
|
||||
{
|
||||
name: "DataMeet India Maps",
|
||||
url: "https://projects.datameet.org",
|
||||
items: [
|
||||
geoJsonItem(
|
||||
"datameet-india-composite",
|
||||
"India Composite Boundary",
|
||||
"DataMeet India community",
|
||||
"https://raw.githubusercontent.com/datameet/maps/master/Country/india-composite.geojson",
|
||||
"https://github.com/datameet/maps/blob/master/Country/india-composite.geojson",
|
||||
"India's national outline, following the boundary the Government of India recognizes (CC BY 4.0, 11 MB).",
|
||||
[
|
||||
[68.16, 6.75],
|
||||
[97.4, 37.1],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "GADM India (geohacker/india)",
|
||||
url: "https://gadm.org",
|
||||
items: [
|
||||
geoJsonItem(
|
||||
"gadm-india-states",
|
||||
"India State & Union Territory Boundaries",
|
||||
"GADM (via geohacker/india)",
|
||||
"https://raw.githubusercontent.com/geohacker/india/master/state/india_state.geojson",
|
||||
"https://github.com/geohacker/india",
|
||||
"35 state and union territory polygons from GADM, drawn before Telangana (2014) and Ladakh (2019) were formed (23 MB).",
|
||||
[
|
||||
[68.18, 6.75],
|
||||
[97.42, 35.51],
|
||||
],
|
||||
),
|
||||
geoJsonItem(
|
||||
"gadm-india-districts",
|
||||
"India District Boundaries",
|
||||
"GADM (via geohacker/india)",
|
||||
"https://raw.githubusercontent.com/geohacker/india/master/district/india_district.geojson",
|
||||
"https://github.com/geohacker/india",
|
||||
"594 district polygons from GADM, with older boundaries: districts created since are missing (35 MB).",
|
||||
[
|
||||
[68.18, 6.75],
|
||||
[97.42, 35.51],
|
||||
],
|
||||
),
|
||||
geoJsonItem(
|
||||
"gadm-india-taluks",
|
||||
"India Taluk & Tehsil Sub-District Boundaries",
|
||||
"GADM (via geohacker/india)",
|
||||
"https://raw.githubusercontent.com/geohacker/india/master/taluk/india_taluk.geojson",
|
||||
"https://github.com/geohacker/india",
|
||||
"2,299 sub-district polygons (tehsils, taluks, mandals and blocks) from GADM (46 MB).",
|
||||
[
|
||||
[68.18, 6.75],
|
||||
[97.42, 35.51],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "geoBoundaries India",
|
||||
url: "https://www.geoboundaries.org",
|
||||
items: [
|
||||
geoJsonItem(
|
||||
"geoboundaries-ind-adm1",
|
||||
"geoBoundaries India States & UTs (ADM1)",
|
||||
"geoBoundaries",
|
||||
`${GEOBOUNDARIES_INDIA}ADM1/geoBoundaries-IND-ADM1_simplified.geojson`,
|
||||
"https://www.geoboundaries.org",
|
||||
"36 simplified state and union territory polygons. Source: DataMeet India community and the Election Commission of India (CC BY 2.5 IN).",
|
||||
[
|
||||
[68.09, 6.75],
|
||||
[97.42, 37.08],
|
||||
],
|
||||
),
|
||||
geoJsonItem(
|
||||
"geoboundaries-ind-adm2",
|
||||
"geoBoundaries India Districts (ADM2)",
|
||||
"geoBoundaries",
|
||||
`${GEOBOUNDARIES_INDIA}ADM2/geoBoundaries-IND-ADM2_simplified.geojson`,
|
||||
"https://www.geoboundaries.org",
|
||||
"735 simplified district polygons. Source: Pathways Data and the Local Government Directory, lgdirectory.gov.in (ODbL 1.0).",
|
||||
[
|
||||
[68.17, 6.75],
|
||||
[97.4, 37.08],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("dl", "Delhi (NCR)", [
|
||||
{
|
||||
name: "Geospatial Delhi Limited (GSDL)",
|
||||
url: "https://gsdl.delhi.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"delhi-state-boundary",
|
||||
"Delhi State Boundary",
|
||||
"Delhi/Delhi_Boundary.geojson",
|
||||
"Boundary of the National Capital Territory (NCT) of Delhi.",
|
||||
[
|
||||
[76.83, 28.4],
|
||||
[77.35, 28.89],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"delhi-mcd-wards",
|
||||
"Delhi Municipal Corporation (MCD) Wards",
|
||||
"Delhi/Delhi_Wards.geojson",
|
||||
"290 municipal wards of Delhi.",
|
||||
[
|
||||
[76.83, 28.4],
|
||||
[77.35, 28.89],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Municipal Corporation Faridabad",
|
||||
url: "https://mcfaridabad.org",
|
||||
items: [
|
||||
datameetItem(
|
||||
"faridabad-ncr-wards",
|
||||
"Faridabad Municipal Corporation Wards",
|
||||
"Faridabad/Faridabad_Wards.geojson",
|
||||
"40 municipal wards of Faridabad, Haryana (NCR).",
|
||||
[
|
||||
[77.21, 28.29],
|
||||
[77.37, 28.52],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"faridabad-ncr-boundary",
|
||||
"Faridabad Municipal Corporation Boundary",
|
||||
"Faridabad/Faridabad_Boundary.geojson",
|
||||
"Municipal corporation limit of Faridabad, Haryana (NCR).",
|
||||
[
|
||||
[77.21, 28.29],
|
||||
[77.37, 28.52],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("ka", "Karnataka", [
|
||||
{
|
||||
name: "Bruhat Bengaluru Mahanagara Palike (BBMP)",
|
||||
url: "https://bbmp.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"bbmp-wards",
|
||||
"Bengaluru BBMP Municipal Wards",
|
||||
"Bangalore/BBMP.geojson",
|
||||
"243 municipal wards of Bengaluru.",
|
||||
[
|
||||
[77.45, 12.83],
|
||||
[77.79, 13.15],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"bbmp-old-wards",
|
||||
"Bengaluru BBMP Previous Wards",
|
||||
"Bangalore/BBMP_oldWards.geojson",
|
||||
"The earlier 198-ward division of Bengaluru.",
|
||||
[
|
||||
[77.46, 12.83],
|
||||
[77.79, 13.15],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Karnataka Geographic Information System (K-GIS)",
|
||||
url: "https://kgis.ksrsac.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"karnataka-districts",
|
||||
"Karnataka District Boundaries",
|
||||
["Karnataka"],
|
||||
"The 27 districts of Karnataka, as mapped by GADM (older boundaries: districts created since are missing). Loads only Karnataka's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[74.05, 11.57],
|
||||
[78.58, 18.46],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("tn", "Tamil Nadu", [
|
||||
{
|
||||
name: "Greater Chennai Corporation (GCC)",
|
||||
url: "https://chennaicorporation.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"chennai-gcc-wards",
|
||||
"Greater Chennai Corporation (GCC) Wards",
|
||||
"Chennai/Wards.geojson",
|
||||
"201 municipal wards of Chennai.",
|
||||
[
|
||||
[80.14, 12.85],
|
||||
[80.34, 13.24],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"chennai-cma-boundary",
|
||||
"Chennai Metropolitan Area (CMA) Boundary",
|
||||
"Chennai/CMA.geojson",
|
||||
"Boundary of the Chennai Metropolitan Area planning region.",
|
||||
[
|
||||
[80.0, 12.85],
|
||||
[80.35, 13.3],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"chennai-zones",
|
||||
"Greater Chennai Corporation Zones",
|
||||
"Chennai/Zones.geojson",
|
||||
"16 administrative zones of Greater Chennai Corporation.",
|
||||
[
|
||||
[80.14, 12.85],
|
||||
[80.34, 13.24],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Tamil Nadu GIS (TNGIS)",
|
||||
url: "https://tngis.tn.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"tamil-nadu-districts",
|
||||
"Tamil Nadu District Boundaries",
|
||||
["Tamil Nadu"],
|
||||
"The 30 districts of Tamil Nadu, as mapped by GADM (older boundaries: districts created since are missing). Loads only Tamil Nadu's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[76.22, 8.07],
|
||||
[80.35, 13.54],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("ts", "Telangana & Andhra Pradesh", [
|
||||
{
|
||||
name: "Greater Hyderabad Municipal Corporation (GHMC)",
|
||||
url: "https://ghmc.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"ghmc-area",
|
||||
"Greater Hyderabad Municipal Corporation (GHMC) Boundary",
|
||||
"Hyderabad/ghmc-area.geojson",
|
||||
"Outline of the Greater Hyderabad Municipal Corporation.",
|
||||
[
|
||||
[78.23, 17.29],
|
||||
[78.63, 17.57],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"ghmc-wards",
|
||||
"Hyderabad GHMC Wards",
|
||||
"Hyderabad/ghmc-wards.geojson",
|
||||
"145 wards of Greater Hyderabad.",
|
||||
[
|
||||
[78.27, 17.29],
|
||||
[78.63, 17.57],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"ghmc-zones",
|
||||
"Hyderabad GHMC Zones",
|
||||
"Hyderabad/ghmc-zones.geojson",
|
||||
"5 administrative zones of Greater Hyderabad.",
|
||||
[
|
||||
[78.23, 17.29],
|
||||
[78.63, 17.57],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Andhra Pradesh GIS Portal",
|
||||
url: "https://core.ap.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"vijayawada-wards",
|
||||
"Vijayawada Municipal Corporation (VMC) Wards",
|
||||
"Vijayawada/Vijayawada_Wards.geojson",
|
||||
"77 municipal wards of Vijayawada.",
|
||||
[
|
||||
[80.58, 16.47],
|
||||
[80.69, 16.59],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"vijayawada-boundary",
|
||||
"Vijayawada City Boundary",
|
||||
"Vijayawada/Vijayawada_Boundary.geojson",
|
||||
"Municipal corporation limit of Vijayawada.",
|
||||
[
|
||||
[80.58, 16.47],
|
||||
[80.69, 16.59],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("mh", "Maharashtra", [
|
||||
{
|
||||
name: "Brihanmumbai Municipal Corporation (BMC)",
|
||||
url: "https://portal.mcgm.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"bmc-wards",
|
||||
"Mumbai Municipal Corporation (BMC/MCGM) Wards",
|
||||
"Mumbai/BMC_Wards.geojson",
|
||||
"24 administrative wards of Mumbai.",
|
||||
[
|
||||
[72.77, 18.89],
|
||||
[72.98, 19.28],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"mumbai-slum-clusters",
|
||||
"Mumbai Slum Clusters",
|
||||
"Mumbai/slumClusters.geojson",
|
||||
"2,542 mapped slum clusters in Mumbai.",
|
||||
[
|
||||
[72.78, 18.9],
|
||||
[72.98, 19.27],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Maharashtra Remote Sensing Application Centre (MRSAC)",
|
||||
url: "https://mrsac.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"maharashtra-districts",
|
||||
"Maharashtra District Boundaries",
|
||||
["Maharashtra"],
|
||||
"The 34 districts of Maharashtra, as mapped by GADM (older boundaries: districts created since are missing). Loads only Maharashtra's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[72.65, 15.6],
|
||||
[80.9, 22.04],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("gj", "Gujarat", [
|
||||
{
|
||||
name: "Ahmedabad Municipal Corporation (AMC)",
|
||||
url: "https://ahmedabadcity.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"amc-wards",
|
||||
"Ahmedabad Municipal Corporation (AMC) Wards",
|
||||
"Ahmedabad/Wards.geojson",
|
||||
"48 municipal wards of Ahmedabad.",
|
||||
[
|
||||
[72.44, 22.91],
|
||||
[72.71, 23.15],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Gujarat State Portal (BISAG-N)",
|
||||
url: "https://bisag-n.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"gujarat-districts",
|
||||
"Gujarat District Boundaries",
|
||||
["Gujarat"],
|
||||
"The 25 districts of Gujarat, as mapped by GADM (older boundaries: districts created since are missing). Loads only Gujarat's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[68.18, 20.12],
|
||||
[74.48, 24.71],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("mp", "Madhya Pradesh", [
|
||||
{
|
||||
name: "Bhopal Municipal Corporation (BMC MP)",
|
||||
url: "https://bhopalbmc.mp.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"bhopal-wards",
|
||||
"Bhopal Municipal Corporation Wards",
|
||||
"Bhopal/Bhopal_wards.geojson",
|
||||
"86 municipal wards of Bhopal.",
|
||||
[
|
||||
[77.26, 23.11],
|
||||
[77.54, 23.34],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"bhopal-zones",
|
||||
"Bhopal Municipal Corporation Zones",
|
||||
"Bhopal/Zone_Boundary.geojson",
|
||||
"19 administrative zones of Bhopal.",
|
||||
[
|
||||
[77.26, 23.11],
|
||||
[77.54, 23.34],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "MP Council of Science and Technology (MPCOST)",
|
||||
url: "https://mpcost.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"madhya-pradesh-districts",
|
||||
"Madhya Pradesh District Boundaries",
|
||||
["Madhya Pradesh"],
|
||||
"The 48 districts of Madhya Pradesh, as mapped by GADM (older boundaries: districts created since are missing). Loads only Madhya Pradesh's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[74.03, 21.07],
|
||||
[82.81, 26.88],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("pb", "Punjab, Haryana & Chandigarh", [
|
||||
{
|
||||
name: "Chandigarh Smart City & Municipal Corporation",
|
||||
url: "https://mcchandigarh.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"chandigarh-sectors",
|
||||
"Chandigarh Sector Boundaries",
|
||||
"Chandigarh/Chandigarh_Sectors.geojson",
|
||||
"63 sectors of Chandigarh.",
|
||||
[
|
||||
[76.71, 30.68],
|
||||
[76.85, 30.78],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"chandigarh-boundary",
|
||||
"Chandigarh Union Territory Boundary",
|
||||
"Chandigarh/Chandigarh_Boundary.geojson",
|
||||
"Outline of the Union Territory of Chandigarh.",
|
||||
[
|
||||
[76.7, 30.66],
|
||||
[76.85, 30.8],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"chandigarh-wards",
|
||||
"Chandigarh Municipal Corporation Wards",
|
||||
"Chandigarh/Chandigarh_Wards.geojson",
|
||||
"28 municipal wards of Chandigarh.",
|
||||
[
|
||||
[76.7, 30.66],
|
||||
[76.85, 30.8],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Punjab Remote Sensing Centre (PRSC)",
|
||||
url: "https://prsc.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"punjab-haryana-districts",
|
||||
"Punjab & Haryana District Boundaries",
|
||||
["Punjab", "Haryana"],
|
||||
"The 36 districts of Punjab and Haryana, as mapped by GADM (older boundaries: districts created since are missing). Loads only Punjab and Haryana's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[73.87, 27.65],
|
||||
[77.6, 32.58],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("wb", "West Bengal", [
|
||||
{
|
||||
name: "Kolkata Municipal Corporation (KMC)",
|
||||
url: "https://kmcgov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"kolkata-wards",
|
||||
"Kolkata Municipal Corporation (KMC) Wards",
|
||||
"Kolkata/kolkata.geojson",
|
||||
"141 municipal wards of Kolkata.",
|
||||
[
|
||||
[88.24, 22.45],
|
||||
[88.46, 22.64],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "West Bengal Spatial Data Infrastructure (WBSDI)",
|
||||
url: "https://wbsdi.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"west-bengal-districts",
|
||||
"West Bengal District Boundaries",
|
||||
["West Bengal"],
|
||||
"The 19 districts of West Bengal, as mapped by GADM (older boundaries: districts created since are missing). Loads only West Bengal's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[85.82, 21.53],
|
||||
[89.88, 27.23],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("or", "Odisha", [
|
||||
{
|
||||
name: "Bhubaneswar Smart City Limited (BSCL)",
|
||||
url: "https://smartcitybhubaneswar.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"bhubaneswar-jurisdiction",
|
||||
"Bhubaneswar Police Jurisdictions",
|
||||
"Bhubaneswar/Police_Jurisdiction.geojson",
|
||||
"17 police station jurisdictions of Bhubaneswar.",
|
||||
[
|
||||
[85.62, 20.06],
|
||||
[86.09, 20.44],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Odisha State Spatial Data Infrastructure (OSSDI)",
|
||||
url: "https://spatialodisha.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"odisha-districts",
|
||||
"Odisha District Boundaries",
|
||||
["Orissa"],
|
||||
"The 30 districts of Odisha, as mapped by GADM (older boundaries: districts created since are missing). Loads only Odisha's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[81.38, 17.8],
|
||||
[87.49, 22.57],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("br", "Bihar & Jharkhand", [
|
||||
{
|
||||
name: "Bodh Gaya Nagar Panchayat",
|
||||
url: "https://bodhgayacity.bihar.gov.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"bodh-gaya-wards",
|
||||
"Bodh Gaya Municipal Wards",
|
||||
"Bodh_Gaya/Bodh_Gaya_Wards.geojson",
|
||||
"19 municipal wards of Bodh Gaya.",
|
||||
[
|
||||
[84.95, 24.67],
|
||||
[85.02, 24.74],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"bodh-gaya-boundary",
|
||||
"Bodh Gaya Civic Boundary",
|
||||
"Bodh_Gaya/Bodh_Gaya_Boundary.geojson",
|
||||
"Town boundary of Bodh Gaya.",
|
||||
[
|
||||
[84.95, 24.67],
|
||||
[85.02, 24.74],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"katihar-wards",
|
||||
"Katihar Municipal Corporation Wards",
|
||||
"Katihar/Katihar_Wards.geojson",
|
||||
"45 municipal wards of Katihar, Bihar.",
|
||||
[
|
||||
[87.54, 25.5],
|
||||
[87.6, 25.6],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"purnia-wards",
|
||||
"Purnia Municipal Corporation Wards",
|
||||
"Purnia/Purnia_Wards.geojson",
|
||||
"43 municipal wards of Purnia, Bihar.",
|
||||
[
|
||||
[87.43, 25.75],
|
||||
[87.55, 25.85],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Bihar Remote Sensing Application Centre (BIRSAC)",
|
||||
url: "https://disastermgmt.bihar.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"bihar-jharkhand-districts",
|
||||
"Bihar & Jharkhand District Boundaries",
|
||||
["Bihar", "Jharkhand"],
|
||||
"The 59 districts of Bihar and Jharkhand, as mapped by GADM (older boundaries: districts created since are missing). Loads only Bihar and Jharkhand's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[83.31, 21.96],
|
||||
[88.3, 27.86],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("rj", "Rajasthan", [
|
||||
{
|
||||
name: "Jaipur Smart City Limited",
|
||||
url: "https://jaipurmc.org",
|
||||
items: [
|
||||
datameetItem(
|
||||
"jaipur-wards",
|
||||
"Jaipur Municipal Corporation Wards",
|
||||
"Jaipur/Jaipur_Wards.geojson",
|
||||
"77 municipal wards of Jaipur.",
|
||||
[
|
||||
[75.68, 26.77],
|
||||
[75.92, 27.03],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"jaipur-boundary",
|
||||
"Jaipur Municipal Boundary",
|
||||
"Jaipur/Jaipur_Boundary.geojson",
|
||||
"Municipal corporation boundary of Jaipur.",
|
||||
[
|
||||
[75.68, 26.77],
|
||||
[75.92, 27.03],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"jaipur-zones",
|
||||
"Jaipur Municipal Zones",
|
||||
"Jaipur/Jaipur_Zones.geojson",
|
||||
"10 administrative zones of Jaipur.",
|
||||
[
|
||||
[75.68, 26.77],
|
||||
[75.92, 27.03],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"kishangarh-wards",
|
||||
"Kishangarh Municipal Wards",
|
||||
"Kishangarh/Kishangarh_Wards.geojson",
|
||||
"45 municipal wards of Kishangarh, Rajasthan.",
|
||||
[
|
||||
[74.79, 26.54],
|
||||
[74.9, 26.64],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rajasthan GIS (Raj-GIS / RISL)",
|
||||
url: "https://gis.rajasthan.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"rajasthan-districts",
|
||||
"Rajasthan District Boundaries",
|
||||
["Rajasthan"],
|
||||
"The 32 districts of Rajasthan, as mapped by GADM (older boundaries: districts created since are missing). Loads only Rajasthan's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[69.48, 23.06],
|
||||
[78.27, 30.2],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("kl", "Kerala", [
|
||||
{
|
||||
name: "Kerala Spatial Data Infrastructure (KSDI)",
|
||||
url: "https://ksdi.kerala.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"kerala-districts",
|
||||
"Kerala District Boundaries",
|
||||
["Kerala"],
|
||||
"The 14 districts of Kerala, as mapped by GADM (older boundaries: districts created since are missing). Loads only Kerala's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[74.86, 8.29],
|
||||
[77.4, 12.8],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("up", "Uttar Pradesh", [
|
||||
{
|
||||
name: "Lucknow Municipal Corporation",
|
||||
url: "https://lmc.up.nic.in",
|
||||
items: [
|
||||
datameetItem(
|
||||
"lucknow-wards",
|
||||
"Lucknow Municipal Corporation Wards",
|
||||
"Lucknow/Lucknow_ward_boundary.geojson",
|
||||
"112 municipal wards of Lucknow.",
|
||||
[
|
||||
[80.83, 26.74],
|
||||
[81.07, 26.94],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"lucknow-boundary",
|
||||
"Lucknow City Boundary",
|
||||
"Lucknow/Lucknow_city_boundary.geojson",
|
||||
"Municipal corporation boundary of Lucknow.",
|
||||
[
|
||||
[80.83, 26.74],
|
||||
[81.07, 26.94],
|
||||
],
|
||||
),
|
||||
datameetItem(
|
||||
"kanpur-boundary",
|
||||
"Kanpur City Boundary",
|
||||
"Kanpur/Kanpur_city_boundaries.geojson",
|
||||
"Municipal boundary of Kanpur.",
|
||||
[
|
||||
[80.19, 26.33],
|
||||
[80.48, 26.54],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Remote Sensing Applications Centre Uttar Pradesh (RSAC-UP)",
|
||||
url: "https://rsacup.org.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"uttar-pradesh-districts",
|
||||
"Uttar Pradesh District Boundaries",
|
||||
["Uttar Pradesh"],
|
||||
"The 70 districts of Uttar Pradesh, as mapped by GADM (older boundaries: districts created since are missing). Loads only Uttar Pradesh's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[77.08, 23.87],
|
||||
[84.64, 30.42],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("ct", "Chhattisgarh", [
|
||||
{
|
||||
name: "Chhattisgarh State Centre (CG-Spatial)",
|
||||
url: "https://chhattisgarh.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"chhattisgarh-districts",
|
||||
"Chhattisgarh District Boundaries",
|
||||
["Chhattisgarh"],
|
||||
"The 16 districts of Chhattisgarh, as mapped by GADM (older boundaries: districts created since are missing). Loads only Chhattisgarh's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[80.23, 17.78],
|
||||
[84.39, 24.11],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("ga", "Goa", [
|
||||
{
|
||||
name: "Goa State Spatial Data Infrastructure",
|
||||
url: "https://goa.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"goa-districts",
|
||||
"Goa District Boundaries",
|
||||
["Goa"],
|
||||
"The 2 districts of Goa, as mapped by GADM (older boundaries: districts created since are missing). Loads only Goa's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[73.67, 14.89],
|
||||
[74.35, 15.8],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
region("as", "Assam & North East", [
|
||||
{
|
||||
name: "Assam Remote Sensing Application Centre (ARSAC)",
|
||||
url: "https://arsac.assam.gov.in",
|
||||
items: [
|
||||
gadmDistricts(
|
||||
"assam-north-east-districts",
|
||||
"Assam & North Eastern Region District Boundaries",
|
||||
[
|
||||
"Assam",
|
||||
"Arunachal Pradesh",
|
||||
"Manipur",
|
||||
"Meghalaya",
|
||||
"Mizoram",
|
||||
"Nagaland",
|
||||
"Sikkim",
|
||||
"Tripura",
|
||||
],
|
||||
"The 78 districts of Assam and the other North Eastern states, as mapped by GADM (older boundaries: districts created since are missing). Loads only Assam and the other North Eastern states's districts from the all-India file, which Download saves.",
|
||||
[
|
||||
[88.01, 21.94],
|
||||
[97.42, 29.47],
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
];
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Feature } from "geojson";
|
||||
import type { GeoLibreAppAPI, GeoLibrePlugin } from "../types";
|
||||
import { addArcGISLayer } from "./arcgis-layer";
|
||||
import {
|
||||
@@ -125,6 +126,12 @@ export interface ArcGisHubCatalog {
|
||||
* since the Socrata catalog has no spatial search.
|
||||
*/
|
||||
socrataDomain?: string;
|
||||
/**
|
||||
* A fixed list of GeoJSON datasets, for a portal with no searchable catalog.
|
||||
* Takes the place of `siteId`/`orgId`: the panel filters the list by keyword
|
||||
* locally, and the "current map area" filter does not apply.
|
||||
*/
|
||||
items?: readonly ArcGisHubItem[];
|
||||
}
|
||||
|
||||
/** A group of catalogs picked together, such as one state's portals. */
|
||||
@@ -212,6 +219,11 @@ export interface ArcGisHubPluginConfig {
|
||||
* catalog's site.
|
||||
*/
|
||||
catalogSets?: readonly ArcGisHubCatalogSet[];
|
||||
/**
|
||||
* Initial catalog set id selected before the user has saved a choice in
|
||||
* `localStorage`. When omitted, the picker starts on its placeholder option.
|
||||
*/
|
||||
defaultCatalogSetId?: string;
|
||||
}
|
||||
|
||||
/** A catalog panel plugin plus the hook that translates its labels. */
|
||||
@@ -328,21 +340,29 @@ export function createArcGisHubPlugin(config: ArcGisHubPluginConfig): ArcGisHubP
|
||||
pickerRestored = true;
|
||||
try {
|
||||
const raw = globalThis.localStorage?.getItem(pickerStorageKey);
|
||||
if (!raw) return;
|
||||
const saved = JSON.parse(raw) as { set?: unknown; catalogs?: unknown };
|
||||
const set = catalogSets.find((candidate) => candidate.id === saved.set);
|
||||
if (set) selectedSetId = set.id;
|
||||
if (saved.catalogs && typeof saved.catalogs === "object") {
|
||||
for (const [setId, catalogId] of Object.entries(saved.catalogs)) {
|
||||
const owner = catalogSets.find((candidate) => candidate.id === setId);
|
||||
if (owner?.catalogs.some((catalog) => catalog.id === catalogId)) {
|
||||
selectedCatalogIds.set(setId, catalogId as string);
|
||||
if (raw) {
|
||||
const saved = JSON.parse(raw) as { set?: unknown; catalogs?: unknown };
|
||||
const set = catalogSets.find((candidate) => candidate.id === saved.set);
|
||||
if (set) selectedSetId = set.id;
|
||||
if (saved.catalogs && typeof saved.catalogs === "object") {
|
||||
for (const [setId, catalogId] of Object.entries(saved.catalogs)) {
|
||||
const owner = catalogSets.find((candidate) => candidate.id === setId);
|
||||
if (owner?.catalogs.some((catalog) => catalog.id === catalogId)) {
|
||||
selectedCatalogIds.set(setId, catalogId as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Storage blocked (private window, sandboxed iframe) or corrupt: start fresh.
|
||||
}
|
||||
if (
|
||||
!selectedSetId &&
|
||||
config.defaultCatalogSetId &&
|
||||
catalogSets.some((candidate) => candidate.id === config.defaultCatalogSetId)
|
||||
) {
|
||||
selectedSetId = config.defaultCatalogSetId;
|
||||
}
|
||||
}
|
||||
|
||||
/** Remember the picker choice; a convenience, so failures are ignored. */
|
||||
@@ -397,6 +417,12 @@ export function createArcGisHubPlugin(config: ArcGisHubPluginConfig): ArcGisHubP
|
||||
if (data?.type !== "FeatureCollection" || !Array.isArray(data.features)) {
|
||||
throw new Error("The item is not valid GeoJSON.");
|
||||
}
|
||||
if (item.featureFilter) {
|
||||
const { property, values } = item.featureFilter;
|
||||
data.features = data.features.filter((feature: Feature) =>
|
||||
values.includes(String(feature.properties?.[property])),
|
||||
);
|
||||
}
|
||||
// deactivate() nulls appRef, which can happen while the fetch above is in
|
||||
// flight; bail explicitly instead of adding a layer to a torn-down host.
|
||||
if (!appRef) return false;
|
||||
@@ -522,7 +548,9 @@ export function createArcGisHubPlugin(config: ArcGisHubPluginConfig): ArcGisHubP
|
||||
catalogSelect.hidden = (set?.catalogs.length ?? 0) < 2;
|
||||
openPortal.disabled = !current;
|
||||
// The Socrata catalog cannot search by extent, so the filter is moot.
|
||||
viewOnly.disabled = Boolean(current?.socrataDomain);
|
||||
viewOnly.disabled = Boolean(
|
||||
current?.socrataDomain || (current?.items && current.items.length > 0),
|
||||
);
|
||||
};
|
||||
syncCatalogs();
|
||||
|
||||
@@ -733,7 +761,10 @@ export function createArcGisHubPlugin(config: ArcGisHubPluginConfig): ArcGisHubP
|
||||
// view-only search with no extent is refused and says so instead. Checked
|
||||
// before the abort below, like the empty-query guard, so a refused search
|
||||
// leaves the running one alone.
|
||||
const viewFilter = viewOnly.checked && !catalog?.socrataDomain;
|
||||
const viewFilter =
|
||||
viewOnly.checked &&
|
||||
!catalog?.socrataDomain &&
|
||||
(!catalog?.items || catalog.items.length === 0);
|
||||
const viewBounds = !append && viewFilter ? (appRef?.getViewBounds?.() ?? null) : null;
|
||||
if (!append && viewFilter && !viewBounds) {
|
||||
status.textContent = labels.viewUnavailable;
|
||||
@@ -762,7 +793,23 @@ export function createArcGisHubPlugin(config: ArcGisHubPluginConfig): ArcGisHubP
|
||||
const signal = controller.signal;
|
||||
let scope: CatalogScope = {};
|
||||
let page: ArcGisHubSearchResult;
|
||||
if (catalog?.socrataDomain) {
|
||||
if (catalog?.items && catalog.items.length > 0) {
|
||||
const queryLower = query.toLowerCase().trim();
|
||||
const filtered = queryLower
|
||||
? catalog.items.filter(
|
||||
(item) =>
|
||||
item.title.toLowerCase().includes(queryLower) ||
|
||||
(item.snippet && item.snippet.toLowerCase().includes(queryLower)) ||
|
||||
(item.description && item.description.toLowerCase().includes(queryLower)) ||
|
||||
(item.owner && item.owner.toLowerCase().includes(queryLower)),
|
||||
)
|
||||
: [...catalog.items];
|
||||
page = {
|
||||
results: filtered.slice(start - 1, start - 1 + PAGE_SIZE),
|
||||
total: filtered.length,
|
||||
nextStart: start + PAGE_SIZE <= filtered.length ? start + PAGE_SIZE : -1,
|
||||
};
|
||||
} else if (catalog?.socrataDomain) {
|
||||
page = await searchSocrataCatalog(catalog.socrataDomain, query, {
|
||||
start,
|
||||
num: PAGE_SIZE,
|
||||
@@ -799,7 +846,13 @@ export function createArcGisHubPlugin(config: ArcGisHubPluginConfig): ArcGisHubP
|
||||
// A site catalog's datasets have pages on that site; an organization
|
||||
// scope may reach items the site does not list, so use the global Hub.
|
||||
// (A Socrata card carries its own page URL.)
|
||||
if (catalog && !append) detailsPageUrl = scope.groups ? catalog.url : undefined;
|
||||
if (catalog && !append) {
|
||||
detailsPageUrl = catalog.items?.length
|
||||
? catalog.url
|
||||
: scope.groups
|
||||
? catalog.url
|
||||
: undefined;
|
||||
}
|
||||
page.results.forEach(renderItem);
|
||||
total = page.total;
|
||||
shown += page.results.length;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { GeoLibrePlugin } from "../types";
|
||||
import {
|
||||
createArcGisHubPlugin,
|
||||
DEFAULT_ARCGIS_HUB_LABELS,
|
||||
type ArcGisHubLabels,
|
||||
} from "./maplibre-arcgis-hub";
|
||||
import { INDIA_GIS_CATALOGS } from "./india-gis-catalogs";
|
||||
|
||||
export const INDIA_GIS_PLUGIN_ID = "geolibre-india-gis";
|
||||
|
||||
export const DEFAULT_INDIA_GIS_LABELS: ArcGisHubLabels = {
|
||||
...DEFAULT_ARCGIS_HUB_LABELS,
|
||||
hint: "Choose a region, then a portal, to browse open GIS data for India. Add layers to the map or download data.",
|
||||
searchPlaceholder: "Search India GIS datasets",
|
||||
noResults: "No matching datasets found for this portal.",
|
||||
searchError: "Could not list this portal's datasets.",
|
||||
catalogSet: "Choose a region",
|
||||
catalog: "Portal",
|
||||
chooseCatalogSet: "Choose a region, then a portal, to list its GIS data.",
|
||||
openPortal: "Open portal",
|
||||
};
|
||||
|
||||
const indiaGis = createArcGisHubPlugin({
|
||||
id: INDIA_GIS_PLUGIN_ID,
|
||||
name: "India GIS",
|
||||
defaultLabels: DEFAULT_INDIA_GIS_LABELS,
|
||||
catalogSets: INDIA_GIS_CATALOGS,
|
||||
defaultCatalogSetId: "national",
|
||||
// Every portal is a short fixed list of GeoJSON datasets, listed whole as
|
||||
// soon as it is picked.
|
||||
browseWithoutKeyword: true,
|
||||
viewOnlyByDefault: false,
|
||||
filenameFallback: "india-gis-data",
|
||||
});
|
||||
|
||||
export const maplibreIndiaGisPlugin: GeoLibrePlugin = indiaGis.plugin;
|
||||
export const setIndiaGisLabels = indiaGis.setLabels;
|
||||
|
||||
export default maplibreIndiaGisPlugin;
|
||||
@@ -42,6 +42,7 @@ export const WEB_SERVICE_PLUGIN_IDS = [
|
||||
"geolibre-us-federal-gis",
|
||||
"geolibre-us-state-gis",
|
||||
"geolibre-us-local-gis",
|
||||
"geolibre-india-gis",
|
||||
"maplibre-gl-socrata",
|
||||
"maplibre-gl-ckan",
|
||||
"geolibre-stac-catalogs",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
// Check that every portal listed by the US State GIS, US Local GIS, and US
|
||||
// Federal GIS panels still answers with datasets. City and county portals move
|
||||
// Check that every portal listed by the US State GIS, US Local GIS, US Federal
|
||||
// GIS, and India GIS panels still answers with datasets. City and county portals move
|
||||
// and get renamed more often than anything else the app links to, and a dead
|
||||
// entry fails quietly (an empty list or "Could not search"), so run this before
|
||||
// a release or when a user reports a portal as broken.
|
||||
@@ -9,6 +9,7 @@
|
||||
// npm run check:gis-portals -- local # only US Local GIS
|
||||
// npm run check:gis-portals -- state # only US State GIS
|
||||
// npm run check:gis-portals -- federal # only US Federal GIS
|
||||
// npm run check:gis-portals -- india # only India GIS (downloads every file)
|
||||
//
|
||||
// Needs network access; it is not part of CI. Exits 1 if any portal fails.
|
||||
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
fetchArcGisHubSiteGroups,
|
||||
searchArcGisHub,
|
||||
} from "../packages/plugins/src/plugins/arcgis-hub-api.ts";
|
||||
import { INDIA_GIS_CATALOGS } from "../packages/plugins/src/plugins/india-gis-catalogs.ts";
|
||||
import { searchSocrataCatalog } from "../packages/plugins/src/plugins/socrata-api.ts";
|
||||
import { US_FEDERAL_GIS_CATALOGS } from "../packages/plugins/src/plugins/us-federal-gis-catalogs.ts";
|
||||
import { US_LOCAL_GIS_CATALOGS } from "../packages/plugins/src/plugins/us-local-gis-catalogs.ts";
|
||||
@@ -25,6 +27,67 @@ import { US_STATE_GIS_CATALOGS } from "../packages/plugins/src/plugins/us-state-
|
||||
const CONCURRENCY = 8;
|
||||
const TYPES = [...ARCGIS_HUB_SEARCH_TYPES, "Map Service", "Image Service"];
|
||||
|
||||
/** One download per GeoJSON URL, shared by the cards that reuse a file. */
|
||||
const geoJsonDownloads = new Map();
|
||||
|
||||
/**
|
||||
* Download a GeoJSON file the way the panel's "Add to map" reads it.
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {Promise<import("geojson").FeatureCollection>}
|
||||
*/
|
||||
function fetchGeoJson(url) {
|
||||
if (!geoJsonDownloads.has(url)) {
|
||||
geoJsonDownloads.set(
|
||||
url,
|
||||
fetch(url).then(async (response) => {
|
||||
if (!response.ok) throw new Error(`${url} answered ${response.status}`);
|
||||
const data = JSON.parse(await response.text());
|
||||
if (data?.type !== "FeatureCollection" || !Array.isArray(data.features)) {
|
||||
throw new Error(`${url} is not a GeoJSON FeatureCollection`);
|
||||
}
|
||||
return data;
|
||||
}),
|
||||
);
|
||||
}
|
||||
return geoJsonDownloads.get(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a fixed-list card loads features in longitude/latitude.
|
||||
*
|
||||
* @param {import("../packages/plugins/src/plugins/arcgis-hub-api.ts").ArcGisHubItem} item
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function checkItem(item) {
|
||||
const data = await fetchGeoJson(item.dataUrl);
|
||||
const filter = item.featureFilter;
|
||||
const features = filter
|
||||
? data.features.filter((feature) =>
|
||||
filter.values.includes(String(feature.properties?.[filter.property])),
|
||||
)
|
||||
: data.features;
|
||||
if (features.length === 0) throw new Error(`${item.id} has no features`);
|
||||
// A projected file (Web Mercator metres) parses fine but renders nowhere.
|
||||
if (!features.every((feature) => isLonLat(feature.geometry?.coordinates))) {
|
||||
throw new Error(`${item.id} is not in longitude/latitude`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether every position in a GeoJSON coordinate array is longitude/latitude.
|
||||
*
|
||||
* @param {unknown} coordinates A geometry's `coordinates`, at any nesting depth.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isLonLat(coordinates) {
|
||||
if (!Array.isArray(coordinates)) return true;
|
||||
if (typeof coordinates[0] === "number") {
|
||||
return Math.abs(coordinates[0]) <= 180 && Math.abs(coordinates[1]) <= 90;
|
||||
}
|
||||
return coordinates.every(isLonLat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count one portal's datasets the way the panel searches them.
|
||||
*
|
||||
@@ -32,6 +95,10 @@ const TYPES = [...ARCGIS_HUB_SEARCH_TYPES, "Map Service", "Image Service"];
|
||||
* @returns {Promise<string>} How the portal was searched and what it held.
|
||||
*/
|
||||
async function checkPortal(catalog) {
|
||||
if (catalog.items?.length) {
|
||||
await Promise.all(catalog.items.map(checkItem));
|
||||
return `${catalog.items.length} GeoJSON files`;
|
||||
}
|
||||
if (catalog.socrataDomain) {
|
||||
const page = await searchSocrataCatalog(catalog.socrataDomain, "", { num: 1 });
|
||||
if (page.results.length === 0) throw new Error("no spatial datasets");
|
||||
@@ -53,9 +120,10 @@ const panels = [
|
||||
["state", US_STATE_GIS_CATALOGS],
|
||||
["local", US_LOCAL_GIS_CATALOGS],
|
||||
["federal", US_FEDERAL_GIS_CATALOGS],
|
||||
["india", INDIA_GIS_CATALOGS],
|
||||
];
|
||||
if (which && !panels.some(([panel]) => panel === which)) {
|
||||
console.error(`Unknown panel "${which}"; expected state, local, or federal.`);
|
||||
console.error(`Unknown panel "${which}"; expected state, local, federal, or india.`);
|
||||
process.exit(2);
|
||||
}
|
||||
const sets = panels
|
||||
|
||||
@@ -94,6 +94,10 @@
|
||||
"description": "The GIS and open-data portals of about 150 large US cities, counties, and regional agencies.",
|
||||
"docs": "web-services.md#us-local-gis"
|
||||
},
|
||||
"geolibre-india-gis": {
|
||||
"description": "Open national, state, and city boundary datasets for India.",
|
||||
"docs": "web-services.md#india-gis"
|
||||
},
|
||||
"maplibre-gl-socrata": {
|
||||
"description": "Search government Socrata open-data portals.",
|
||||
"docs": "web-services.md#socrata"
|
||||
|
||||
@@ -1374,6 +1374,38 @@ describe("addArcGISLayer (feature layer)", () => {
|
||||
assert.equal(layer?.geojson?.features.length, 1);
|
||||
assert.ok(fetchUrls.some((url) => url.startsWith(`${serviceUrl}/query`)));
|
||||
});
|
||||
|
||||
it("resolves the first feature layer of a MapServer root registered as a Feature Service item", async () => {
|
||||
const mapServerUrl = "https://livingatlas.esri.in/server/rest/services/Cities/Noida/MapServer";
|
||||
const fetchUrls: string[] = [];
|
||||
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
||||
const url = typeof input === "string" ? input : input.toString();
|
||||
fetchUrls.push(url);
|
||||
if (url.includes("/content/items/")) return jsonResponse({ url: mapServerUrl });
|
||||
if (url.startsWith(`${mapServerUrl}?`)) {
|
||||
return jsonResponse({
|
||||
layers: [
|
||||
{ id: 0, type: "Group Layer", subLayerIds: [1, 2] },
|
||||
{ id: 1, type: "Raster Layer", subLayerIds: null },
|
||||
{ id: 2, type: "Feature Layer", subLayerIds: null },
|
||||
],
|
||||
});
|
||||
}
|
||||
return jsonResponse(url.includes("/query") ? QUERY_GEOJSON : LAYER_INFO);
|
||||
}) as typeof fetch;
|
||||
|
||||
const id = await addArcGISLayer(app, {
|
||||
layerType: "feature",
|
||||
sourceType: "portal-item",
|
||||
itemId: "29a492c3a6174bffac6a37fd134b1756",
|
||||
name: "Noida City Boundary",
|
||||
});
|
||||
|
||||
const layer = useAppStore.getState().layers.find((l) => l.id === id);
|
||||
assert.equal(layer?.type, "geojson");
|
||||
assert.equal(layer?.geojson?.features.length, 1);
|
||||
assert.ok(fetchUrls.some((url) => url.startsWith(`${mapServerUrl}/2/query`)));
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps pending viewport edits and their baseline when the map moves", async () => {
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { parseHTML } from "linkedom";
|
||||
import { INDIA_GIS_CATALOGS } from "../packages/plugins/src/plugins/india-gis-catalogs";
|
||||
import {
|
||||
DEFAULT_INDIA_GIS_LABELS,
|
||||
INDIA_GIS_PLUGIN_ID,
|
||||
maplibreIndiaGisPlugin,
|
||||
} from "../packages/plugins/src/plugins/maplibre-india-gis";
|
||||
import { WEB_SERVICE_PLUGIN_IDS } from "../packages/plugins/src/plugins/web-service-sync";
|
||||
import type { GeoLibreAppAPI } from "../packages/plugins/src/types";
|
||||
|
||||
describe("India GIS catalog", () => {
|
||||
it("defines state and national catalog sets with unique ids and valid scopes", () => {
|
||||
assert.ok(INDIA_GIS_CATALOGS.length >= 18);
|
||||
const setIds = INDIA_GIS_CATALOGS.map((set) => set.id);
|
||||
assert.equal(new Set(setIds).size, setIds.length, "Catalog set IDs must be unique");
|
||||
|
||||
const catalogIds = new Set<string>();
|
||||
const itemIds = new Set<string>();
|
||||
const urls = new Set<string>();
|
||||
for (const set of INDIA_GIS_CATALOGS) {
|
||||
assert.ok(set.catalogs.length > 0, `${set.name} has at least one portal`);
|
||||
for (const catalog of set.catalogs) {
|
||||
assert.ok(!catalogIds.has(catalog.id), `Catalog id ${catalog.id} is unique`);
|
||||
catalogIds.add(catalog.id);
|
||||
assert.ok(!urls.has(catalog.url), `Catalog URL ${catalog.url} is unique`);
|
||||
urls.add(catalog.url);
|
||||
assert.match(catalog.url, /^https:\/\/[^/]+$/);
|
||||
assert.ok(catalog.name.length > 0);
|
||||
// Every portal is a fixed list of GeoJSON files, never a search that
|
||||
// could widen to all of ArcGIS Online.
|
||||
assert.equal(catalog.orgId, undefined);
|
||||
assert.equal(catalog.siteId, undefined);
|
||||
assert.ok(catalog.items && catalog.items.length > 0, `${catalog.name} lists datasets`);
|
||||
for (const item of catalog.items) {
|
||||
assert.ok(!itemIds.has(item.id), `Item id ${item.id} is unique`);
|
||||
itemIds.add(item.id);
|
||||
assert.equal(item.type, "GeoJson");
|
||||
// raw.githubusercontent.com serves an LFS pointer for geoBoundaries.
|
||||
assert.doesNotMatch(item.dataUrl ?? "", /raw\.githubusercontent\.com\/wmgeolab/);
|
||||
assert.match(item.dataUrl ?? "", /^https:\/\/.+\.geojson$/);
|
||||
assert.match(item.pageUrl ?? "", /^https:\/\//);
|
||||
assert.ok(item.owner && item.snippet, `${item.id} names its source`);
|
||||
if (item.dataUrl?.includes("datameet/Municipal_Spatial_Data")) {
|
||||
assert.match(item.snippet ?? "", /DataMeet/, `${item.id} credits DataMeet`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("cuts each state's districts out of the shared all-India file", () => {
|
||||
const districtCards = INDIA_GIS_CATALOGS.flatMap((set) =>
|
||||
set.catalogs.flatMap((catalog) => catalog.items ?? []),
|
||||
).filter((item) => item.dataUrl?.endsWith("/district/india_district.geojson"));
|
||||
const filtered = districtCards.filter((item) => item.featureFilter);
|
||||
// Only the national card loads the whole file.
|
||||
assert.equal(districtCards.length - filtered.length, 1);
|
||||
for (const item of filtered) {
|
||||
assert.equal(item.featureFilter?.property, "NAME_1");
|
||||
assert.ok(item.featureFilter?.values.length, `${item.id} names its states`);
|
||||
}
|
||||
});
|
||||
|
||||
it("is registered as an official Web Services plugin", () => {
|
||||
assert.equal(INDIA_GIS_PLUGIN_ID, "geolibre-india-gis");
|
||||
assert.equal(maplibreIndiaGisPlugin.id, INDIA_GIS_PLUGIN_ID);
|
||||
assert.equal(maplibreIndiaGisPlugin.name, "India GIS");
|
||||
assert.ok(WEB_SERVICE_PLUGIN_IDS.includes(INDIA_GIS_PLUGIN_ID));
|
||||
});
|
||||
});
|
||||
|
||||
describe("India GIS panel", () => {
|
||||
const settle = async () => {
|
||||
for (let i = 0; i < 20; i += 1) await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
};
|
||||
|
||||
it("opens on the national boundaries, switches portals, and adds one state's districts", async () => {
|
||||
const { document, window } = parseHTML("<html><body></body></html>");
|
||||
Object.assign(globalThis, { document, window });
|
||||
const originalStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
|
||||
const storage = new Map<string, string>();
|
||||
Object.defineProperty(globalThis, "localStorage", {
|
||||
configurable: true,
|
||||
value: {
|
||||
getItem: (key: string) => storage.get(key) ?? null,
|
||||
setItem: (key: string, value: string) => storage.set(key, value),
|
||||
},
|
||||
});
|
||||
const originalFetch = globalThis.fetch;
|
||||
const requests: string[] = [];
|
||||
const district = (state: string, x: number) => ({
|
||||
type: "Feature",
|
||||
properties: { NAME_1: state, NAME_2: `${state} district` },
|
||||
geometry: { type: "Point", coordinates: [x, 15] },
|
||||
});
|
||||
globalThis.fetch = (async (input: string | URL | Request) => {
|
||||
requests.push(String(input));
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
type: "FeatureCollection",
|
||||
features: [district("Karnataka", 76), district("Kerala", 76.5), district("Goa", 74)],
|
||||
}),
|
||||
);
|
||||
}) as typeof fetch;
|
||||
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
const added: Array<[string, { features: unknown[] }]> = [];
|
||||
const app = {
|
||||
registerRightPanel: (panel: { render: (el: HTMLElement) => void }) => {
|
||||
panel.render(container);
|
||||
return () => {};
|
||||
},
|
||||
openRightPanel: () => {},
|
||||
closeRightPanel: () => {},
|
||||
getViewBounds: () => null,
|
||||
addGeoJsonLayer: (name: string, data: unknown) => {
|
||||
added.push([name, data as { features: unknown[] }]);
|
||||
return "layer-1";
|
||||
},
|
||||
fitBounds: () => {},
|
||||
} as unknown as GeoLibreAppAPI;
|
||||
|
||||
try {
|
||||
maplibreIndiaGisPlugin.activate(app);
|
||||
await settle();
|
||||
|
||||
// Default catalog set ("national") should be selected and loaded automatically on open.
|
||||
const [setSelect, catalogSelect] = Array.from(container.querySelectorAll("select"));
|
||||
assert.equal(setSelect.value, "national");
|
||||
assert.match(container.textContent ?? "", /India Composite Boundary/);
|
||||
assert.match(container.textContent ?? "", new RegExp(DEFAULT_INDIA_GIS_LABELS.showing(1, 1)));
|
||||
|
||||
const pick = (select: HTMLSelectElement, value: string) => {
|
||||
const option = Array.from(select.options).find((candidate) => candidate.value === value);
|
||||
assert.ok(option, value);
|
||||
option.selected = true;
|
||||
select.dispatchEvent(new window.Event("change"));
|
||||
};
|
||||
|
||||
// Switch to Delhi (NCR) -> Geospatial Delhi Limited (GSDL)
|
||||
pick(setSelect, "dl");
|
||||
await settle();
|
||||
pick(catalogSelect, "dl-geospatial-delhi-limited-gsdl");
|
||||
await settle();
|
||||
|
||||
assert.match(container.textContent ?? "", /Delhi State Boundary/);
|
||||
assert.match(container.textContent ?? "", /Delhi Municipal Corporation \(MCD\) Wards/);
|
||||
assert.match(container.textContent ?? "", new RegExp(DEFAULT_INDIA_GIS_LABELS.showing(2, 2)));
|
||||
// Static lists are filtered locally, never searched on ArcGIS Online.
|
||||
assert.equal(requests.length, 0);
|
||||
|
||||
// A state's district card loads only that state's districts.
|
||||
pick(setSelect, "ka");
|
||||
await settle();
|
||||
pick(catalogSelect, "ka-karnataka-geographic-information-system-k-gis");
|
||||
await settle();
|
||||
const addButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(candidate) => candidate.textContent === DEFAULT_INDIA_GIS_LABELS.add,
|
||||
) as HTMLButtonElement;
|
||||
addButton.click();
|
||||
await settle();
|
||||
assert.equal(requests.length, 1);
|
||||
assert.match(requests[0], /\/district\/india_district\.geojson$/);
|
||||
assert.equal(added.length, 1);
|
||||
assert.equal(added[0][0], "Karnataka District Boundaries");
|
||||
assert.deepEqual(added[0][1].features, [district("Karnataka", 76)]);
|
||||
} finally {
|
||||
maplibreIndiaGisPlugin.deactivate?.(app);
|
||||
globalThis.fetch = originalFetch;
|
||||
if (originalStorage) Object.defineProperty(globalThis, "localStorage", originalStorage);
|
||||
else delete (globalThis as { localStorage?: unknown }).localStorage;
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user