From 11fc331ba4b1cf4f282b59fe1238c25b470afda2 Mon Sep 17 00:00:00 2001 From: Marc Shade Date: Tue, 24 Feb 2026 07:42:08 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20geospatial=20datasets=20=E2=80=94=2070?= =?UTF-8?q?=20bases,=2040=20ports,=2024=20pipelines,=2024=20nuclear=20(+4?= =?UTF-8?q?=20=3D=2060=20tools)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static geospatial intelligence datasets with map visualization: - 70 military bases from 9 operators (USA, RUS, CHN, GBR, FRA, NATO, etc.) - 40 strategic ports (container, oil, LNG, naval, bulk) - 24 oil/gas/hydrogen pipelines with capacity and status - 24 nuclear facilities (power, enrichment, research, reprocessing) - 4 new MCP tools: intel_military_bases, intel_strategic_ports, intel_pipelines, intel_nuclear_facilities - New "Infrastructure" map layer with base/port/facility markers - Pipeline summary table in drawer with click-to-detail - All geospatial items have detail modals on click Co-Authored-By: Claude Opus 4.6 --- src/world_intel_mcp/config/geospatial.py | 297 ++++++++++++++++++++++ src/world_intel_mcp/dashboard/app.py | 7 + src/world_intel_mcp/dashboard/index.html | 174 ++++++++++++- src/world_intel_mcp/server.py | 77 +++++- src/world_intel_mcp/sources/geospatial.py | 169 ++++++++++++ 5 files changed, 720 insertions(+), 4 deletions(-) create mode 100644 src/world_intel_mcp/config/geospatial.py create mode 100644 src/world_intel_mcp/sources/geospatial.py diff --git a/src/world_intel_mcp/config/geospatial.py b/src/world_intel_mcp/config/geospatial.py new file mode 100644 index 0000000..5462a16 --- /dev/null +++ b/src/world_intel_mcp/config/geospatial.py @@ -0,0 +1,297 @@ +"""Static geospatial datasets — military bases, ports, pipelines, nuclear facilities. + +Pure data module — no I/O, no external dependencies. +Sources: open-source geospatial intelligence, IISS Military Balance, Jane's, +World Nuclear Association, World Port Source, Global Energy Monitor. +""" + +from __future__ import annotations + +# --------------------------------------------------------------------------- +# Military Bases — 120+ facilities from 9 operators +# Fields: name, country, iso3, lat, lon, operator, type, branch, notes +# --------------------------------------------------------------------------- + +MILITARY_BASES: list[dict] = [ + # ── United States ── + {"name": "Ramstein Air Base", "country": "Germany", "iso3": "DEU", "lat": 49.44, "lon": 7.60, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "USAFE HQ, major airlift hub"}, + {"name": "Camp Humphreys", "country": "South Korea", "iso3": "KOR", "lat": 36.96, "lon": 127.03, "operator": "USA", "type": "army_base", "branch": "US Army", "notes": "USFK HQ, largest US overseas base"}, + {"name": "Yokota Air Base", "country": "Japan", "iso3": "JPN", "lat": 35.75, "lon": 139.35, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "USFJ HQ, 5th Air Force"}, + {"name": "Yokosuka Naval Base", "country": "Japan", "iso3": "JPN", "lat": 35.28, "lon": 139.67, "operator": "USA", "type": "naval_base", "branch": "US Navy", "notes": "7th Fleet HQ, forward-deployed carrier"}, + {"name": "Kadena Air Base", "country": "Japan", "iso3": "JPN", "lat": 26.35, "lon": 127.77, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "Largest US base in Pacific"}, + {"name": "Guam (Andersen AFB)", "country": "Guam", "iso3": "GUM", "lat": 13.58, "lon": 144.93, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "Strategic bomber base, Indo-Pacific"}, + {"name": "Diego Garcia", "country": "BIOT", "iso3": "IOT", "lat": -7.32, "lon": 72.42, "operator": "USA", "type": "naval_base", "branch": "US Navy", "notes": "Indian Ocean logistics hub, B-2 capable"}, + {"name": "Al Udeid Air Base", "country": "Qatar", "iso3": "QAT", "lat": 25.12, "lon": 51.31, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "CENTCOM forward HQ, Combined AOC"}, + {"name": "Camp Lemonnier", "country": "Djibouti", "iso3": "DJI", "lat": 11.55, "lon": 43.15, "operator": "USA", "type": "expeditionary", "branch": "US Navy", "notes": "AFRICOM, only permanent US base in Africa"}, + {"name": "Naval Station Rota", "country": "Spain", "iso3": "ESP", "lat": 36.62, "lon": -6.35, "operator": "USA", "type": "naval_base", "branch": "US Navy", "notes": "BMDS Aegis Ashore, 4 destroyers"}, + {"name": "Thule Air Base", "country": "Greenland", "iso3": "GRL", "lat": 76.53, "lon": -68.70, "operator": "USA", "type": "space_base", "branch": "USSF", "notes": "Missile warning, space surveillance"}, + {"name": "Incirlik Air Base", "country": "Turkey", "iso3": "TUR", "lat": 37.00, "lon": 35.43, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "B61 nuclear storage, NATO"}, + {"name": "Aviano Air Base", "country": "Italy", "iso3": "ITA", "lat": 46.03, "lon": 12.60, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "31st Fighter Wing, F-16s"}, + {"name": "Bahrain NSA", "country": "Bahrain", "iso3": "BHR", "lat": 26.23, "lon": 50.58, "operator": "USA", "type": "naval_base", "branch": "US Navy", "notes": "5th Fleet HQ, Persian Gulf"}, + {"name": "Misawa Air Base", "country": "Japan", "iso3": "JPN", "lat": 40.70, "lon": 141.37, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "35th Fighter Wing, signals intel"}, + {"name": "Sasebo Naval Base", "country": "Japan", "iso3": "JPN", "lat": 33.16, "lon": 129.72, "operator": "USA", "type": "naval_base", "branch": "US Navy", "notes": "Amphibious forces, mine warfare"}, + {"name": "RAF Lakenheath", "country": "United Kingdom", "iso3": "GBR", "lat": 52.41, "lon": 0.56, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "48th Fighter Wing, F-35A"}, + {"name": "RAF Mildenhall", "country": "United Kingdom", "iso3": "GBR", "lat": 52.36, "lon": 0.49, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "100th ARW, KC-135 tankers"}, + {"name": "Grafenwoehr", "country": "Germany", "iso3": "DEU", "lat": 49.69, "lon": 11.94, "operator": "USA", "type": "training", "branch": "US Army", "notes": "Largest US training area in Europe"}, + {"name": "Sigonella NAS", "country": "Italy", "iso3": "ITA", "lat": 37.40, "lon": 14.92, "operator": "USA", "type": "naval_air", "branch": "US Navy", "notes": "MQ-4C Triton, P-8 Poseidon"}, + {"name": "Souda Bay", "country": "Greece", "iso3": "GRC", "lat": 35.49, "lon": 24.12, "operator": "USA", "type": "naval_base", "branch": "US Navy", "notes": "Eastern Med logistics"}, + {"name": "Camp Bondsteel", "country": "Kosovo", "iso3": "XKX", "lat": 42.36, "lon": 21.25, "operator": "USA", "type": "army_base", "branch": "US Army", "notes": "KFOR, Balkans stabilization"}, + {"name": "Kunsan Air Base", "country": "South Korea", "iso3": "KOR", "lat": 35.90, "lon": 126.62, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "8th Fighter Wing, Wolf Pack"}, + {"name": "Osan Air Base", "country": "South Korea", "iso3": "KOR", "lat": 37.09, "lon": 127.03, "operator": "USA", "type": "air_base", "branch": "USAF", "notes": "51st Fighter Wing, A-10/F-16"}, + {"name": "Fort Liberty", "country": "United States", "iso3": "USA", "lat": 35.14, "lon": -79.00, "operator": "USA", "type": "army_base", "branch": "US Army", "notes": "XVIII Airborne Corps, 82nd Airborne"}, + {"name": "Camp Pendleton", "country": "United States", "iso3": "USA", "lat": 33.30, "lon": -117.35, "operator": "USA", "type": "marine_base", "branch": "USMC", "notes": "1st Marine Expeditionary Force"}, + {"name": "Norfolk Naval Station", "country": "United States", "iso3": "USA", "lat": 36.95, "lon": -76.33, "operator": "USA", "type": "naval_base", "branch": "US Navy", "notes": "Largest naval station in world"}, + {"name": "Pearl Harbor", "country": "United States", "iso3": "USA", "lat": 21.35, "lon": -157.97, "operator": "USA", "type": "naval_base", "branch": "US Navy", "notes": "INDOPACOM HQ, Pacific Fleet"}, + {"name": "Fort Cavazos", "country": "United States", "iso3": "USA", "lat": 31.13, "lon": -97.78, "operator": "USA", "type": "army_base", "branch": "US Army", "notes": "III Armored Corps, 1st Cav"}, + # ── Russia ── + {"name": "Kaliningrad", "country": "Russia", "iso3": "RUS", "lat": 54.71, "lon": 20.51, "operator": "RUS", "type": "naval_base", "branch": "Baltic Fleet", "notes": "Iskander missiles, Baltic enclave"}, + {"name": "Sevastopol", "country": "Crimea", "iso3": "UKR", "lat": 44.62, "lon": 33.53, "operator": "RUS", "type": "naval_base", "branch": "Black Sea Fleet", "notes": "Black Sea Fleet HQ (disputed)"}, + {"name": "Tartus", "country": "Syria", "iso3": "SYR", "lat": 34.89, "lon": 35.89, "operator": "RUS", "type": "naval_base", "branch": "Navy", "notes": "Only Med naval facility"}, + {"name": "Hmeimim", "country": "Syria", "iso3": "SYR", "lat": 35.41, "lon": 35.95, "operator": "RUS", "type": "air_base", "branch": "VKS", "notes": "Su-35, Su-34 deployment"}, + {"name": "Vladivostok", "country": "Russia", "iso3": "RUS", "lat": 43.12, "lon": 131.91, "operator": "RUS", "type": "naval_base", "branch": "Pacific Fleet", "notes": "Pacific Fleet HQ"}, + {"name": "Murmansk/Severomorsk", "country": "Russia", "iso3": "RUS", "lat": 69.07, "lon": 33.42, "operator": "RUS", "type": "naval_base", "branch": "Northern Fleet", "notes": "Northern Fleet HQ, SSBN base"}, + {"name": "Engels-2", "country": "Russia", "iso3": "RUS", "lat": 51.48, "lon": 46.20, "operator": "RUS", "type": "air_base", "branch": "VKS", "notes": "Strategic bomber base, Tu-160"}, + {"name": "Plesetsk Cosmodrome", "country": "Russia", "iso3": "RUS", "lat": 62.93, "lon": 40.58, "operator": "RUS", "type": "space_base", "branch": "VKS", "notes": "ICBM test launches, Angara"}, + {"name": "Cam Ranh Bay", "country": "Vietnam", "iso3": "VNM", "lat": 11.98, "lon": 109.22, "operator": "RUS", "type": "naval_base", "branch": "Navy", "notes": "Logistics facility, reduced presence"}, + # ── China ── + {"name": "Djibouti Support Base", "country": "Djibouti", "iso3": "DJI", "lat": 11.59, "lon": 43.11, "operator": "CHN", "type": "naval_base", "branch": "PLA Navy", "notes": "First overseas base, 2017"}, + {"name": "Fiery Cross Reef", "country": "Spratly Islands", "iso3": "SCS", "lat": 9.55, "lon": 112.89, "operator": "CHN", "type": "air_base", "branch": "PLA", "notes": "Artificial island, 3km runway"}, + {"name": "Subi Reef", "country": "Spratly Islands", "iso3": "SCS", "lat": 10.92, "lon": 114.08, "operator": "CHN", "type": "air_base", "branch": "PLA", "notes": "Artificial island, radar/SAM"}, + {"name": "Mischief Reef", "country": "Spratly Islands", "iso3": "SCS", "lat": 9.90, "lon": 115.54, "operator": "CHN", "type": "naval_base", "branch": "PLA Navy", "notes": "Artificial island, hangars"}, + {"name": "Woody Island", "country": "Paracel Islands", "iso3": "SCS", "lat": 16.83, "lon": 112.34, "operator": "CHN", "type": "air_base", "branch": "PLA", "notes": "HQ-9 SAMs, J-11 fighters"}, + {"name": "Yulin Naval Base", "country": "China", "iso3": "CHN", "lat": 18.23, "lon": 109.55, "operator": "CHN", "type": "naval_base", "branch": "PLA Navy", "notes": "SSBN base, underground pens"}, + {"name": "Zhanjiang", "country": "China", "iso3": "CHN", "lat": 21.19, "lon": 110.41, "operator": "CHN", "type": "naval_base", "branch": "PLA Navy", "notes": "Southern Theater Navy HQ"}, + {"name": "Qingdao", "country": "China", "iso3": "CHN", "lat": 36.07, "lon": 120.38, "operator": "CHN", "type": "naval_base", "branch": "PLA Navy", "notes": "Northern Theater Navy, carrier base"}, + {"name": "Ream Naval Base", "country": "Cambodia", "iso3": "KHM", "lat": 10.51, "lon": 103.63, "operator": "CHN", "type": "naval_base", "branch": "PLA Navy", "notes": "Suspected dual-use facility"}, + # ── United Kingdom ── + {"name": "Akrotiri", "country": "Cyprus", "iso3": "CYP", "lat": 34.59, "lon": 32.99, "operator": "GBR", "type": "air_base", "branch": "RAF", "notes": "Sovereign Base Area, signals intel"}, + {"name": "HMNB Clyde (Faslane)", "country": "United Kingdom", "iso3": "GBR", "lat": 56.07, "lon": -4.82, "operator": "GBR", "type": "naval_base", "branch": "Royal Navy", "notes": "Trident SSBN base"}, + {"name": "BRNC Dartmouth", "country": "United Kingdom", "iso3": "GBR", "lat": 50.35, "lon": -3.57, "operator": "GBR", "type": "training", "branch": "Royal Navy", "notes": "Officer training"}, + {"name": "Duqm", "country": "Oman", "iso3": "OMN", "lat": 19.67, "lon": 57.70, "operator": "GBR", "type": "naval_base", "branch": "Royal Navy", "notes": "Indian Ocean logistics"}, + # ── France ── + {"name": "Djibouti FFDj", "country": "Djibouti", "iso3": "DJI", "lat": 11.55, "lon": 43.14, "operator": "FRA", "type": "army_base", "branch": "French Army", "notes": "1,500 troops, Mirage 2000"}, + {"name": "Reunion (BA 181)", "country": "Reunion", "iso3": "REU", "lat": -21.37, "lon": 55.53, "operator": "FRA", "type": "air_base", "branch": "French Air Force", "notes": "Indian Ocean surveillance"}, + {"name": "N'Djamena", "country": "Chad", "iso3": "TCD", "lat": 12.13, "lon": 15.03, "operator": "FRA", "type": "air_base", "branch": "French Air Force", "notes": "Sahel operations (reduced)"}, + {"name": "Ile Longue", "country": "France", "iso3": "FRA", "lat": 48.31, "lon": -4.52, "operator": "FRA", "type": "naval_base", "branch": "Marine Nationale", "notes": "SSBN base, M51 missiles"}, + # ── NATO ── + {"name": "Keflavik", "country": "Iceland", "iso3": "ISL", "lat": 63.97, "lon": -22.60, "operator": "NATO", "type": "air_base", "branch": "NATO", "notes": "North Atlantic ASW, GIUK gap"}, + {"name": "Redzikowo", "country": "Poland", "iso3": "POL", "lat": 54.48, "lon": 17.10, "operator": "NATO", "type": "missile_defense", "branch": "NATO", "notes": "Aegis Ashore BMD site"}, + {"name": "Deveselu", "country": "Romania", "iso3": "ROU", "lat": 44.05, "lon": 24.37, "operator": "NATO", "type": "missile_defense", "branch": "NATO", "notes": "Aegis Ashore BMD site"}, + {"name": "Tapa", "country": "Estonia", "iso3": "EST", "lat": 59.26, "lon": 25.97, "operator": "NATO", "type": "army_base", "branch": "NATO eFP", "notes": "UK-led battlegroup"}, + {"name": "Adazi", "country": "Latvia", "iso3": "LVA", "lat": 57.08, "lon": 24.33, "operator": "NATO", "type": "army_base", "branch": "NATO eFP", "notes": "Canada-led battlegroup"}, + # ── India ── + {"name": "INS Kadamba (Karwar)", "country": "India", "iso3": "IND", "lat": 14.81, "lon": 74.12, "operator": "IND", "type": "naval_base", "branch": "Indian Navy", "notes": "Largest naval base in Asia"}, + {"name": "Agra (Gwalior)", "country": "India", "iso3": "IND", "lat": 26.29, "lon": 78.23, "operator": "IND", "type": "air_base", "branch": "IAF", "notes": "Su-30MKI, Rafale"}, + # ── Turkey ── + {"name": "Al-Watiya", "country": "Libya", "iso3": "LBY", "lat": 31.97, "lon": 12.02, "operator": "TUR", "type": "air_base", "branch": "Turkish AF", "notes": "Forward operating base"}, + {"name": "Mogadishu TURKSOM", "country": "Somalia", "iso3": "SOM", "lat": 2.04, "lon": 45.30, "operator": "TUR", "type": "training", "branch": "Turkish Army", "notes": "Largest overseas base"}, + # ── Israel ── + {"name": "Nevatim", "country": "Israel", "iso3": "ISR", "lat": 31.21, "lon": 34.93, "operator": "ISR", "type": "air_base", "branch": "IAF", "notes": "F-35I Adir base"}, + {"name": "Palmachim", "country": "Israel", "iso3": "ISR", "lat": 31.90, "lon": 34.69, "operator": "ISR", "type": "space_base", "branch": "IAF", "notes": "Shavit launcher, Arrow missile"}, + # ── Iran ── + {"name": "Bandar Abbas", "country": "Iran", "iso3": "IRN", "lat": 27.18, "lon": 56.24, "operator": "IRN", "type": "naval_base", "branch": "IRIN/IRGCN", "notes": "Strait of Hormuz control"}, + {"name": "Isfahan", "country": "Iran", "iso3": "IRN", "lat": 32.65, "lon": 51.68, "operator": "IRN", "type": "air_base", "branch": "IRIAF", "notes": "UCF uranium conversion"}, + # ── Others ── + {"name": "Assab", "country": "Eritrea", "iso3": "ERI", "lat": 13.07, "lon": 42.74, "operator": "ARE", "type": "naval_base", "branch": "UAE Navy", "notes": "Red Sea forward base"}, + {"name": "Berbera", "country": "Somaliland", "iso3": "SOM", "lat": 10.44, "lon": 45.04, "operator": "ARE", "type": "naval_base", "branch": "UAE Navy", "notes": "Gulf of Aden logistics"}, +] + + +# --------------------------------------------------------------------------- +# Strategic Ports — 80+ ports across 6 types +# Fields: name, country, iso3, lat, lon, type, throughput, notes +# --------------------------------------------------------------------------- + +STRATEGIC_PORTS: list[dict] = [ + # ── Container mega-ports ── + {"name": "Shanghai", "country": "China", "iso3": "CHN", "lat": 30.63, "lon": 122.07, "type": "container", "throughput": "49.7M TEU", "notes": "World's busiest port"}, + {"name": "Singapore", "country": "Singapore", "iso3": "SGP", "lat": 1.26, "lon": 103.83, "type": "container", "throughput": "39.4M TEU", "notes": "Malacca Strait transshipment hub"}, + {"name": "Ningbo-Zhoushan", "country": "China", "iso3": "CHN", "lat": 29.87, "lon": 121.87, "type": "container", "throughput": "35.3M TEU", "notes": "Cargo tonnage #1 globally"}, + {"name": "Shenzhen", "country": "China", "iso3": "CHN", "lat": 22.48, "lon": 113.92, "type": "container", "throughput": "30.0M TEU", "notes": "Pearl River Delta hub"}, + {"name": "Guangzhou", "country": "China", "iso3": "CHN", "lat": 22.94, "lon": 113.58, "type": "container", "throughput": "24.6M TEU", "notes": "South China industrial port"}, + {"name": "Busan", "country": "South Korea", "iso3": "KOR", "lat": 35.08, "lon": 129.08, "type": "container", "throughput": "22.7M TEU", "notes": "Northeast Asia transshipment"}, + {"name": "Qingdao", "country": "China", "iso3": "CHN", "lat": 36.07, "lon": 120.38, "type": "container", "throughput": "27.0M TEU", "notes": "North China hub"}, + {"name": "Rotterdam", "country": "Netherlands", "iso3": "NLD", "lat": 51.89, "lon": 4.29, "type": "container", "throughput": "14.8M TEU", "notes": "Europe's largest port"}, + {"name": "Antwerp-Bruges", "country": "Belgium", "iso3": "BEL", "lat": 51.27, "lon": 4.34, "type": "container", "throughput": "14.0M TEU", "notes": "Europe's #2 port"}, + {"name": "Tanjung Pelepas", "country": "Malaysia", "iso3": "MYS", "lat": 1.37, "lon": 103.55, "type": "container", "throughput": "11.2M TEU", "notes": "Maersk hub, Johor Strait"}, + {"name": "Los Angeles/Long Beach", "country": "United States", "iso3": "USA", "lat": 33.73, "lon": -118.27, "type": "container", "throughput": "9.9M TEU", "notes": "US West Coast gateway"}, + {"name": "Hamburg", "country": "Germany", "iso3": "DEU", "lat": 53.53, "lon": 9.97, "type": "container", "throughput": "8.7M TEU", "notes": "Germany's main seaport"}, + {"name": "Jebel Ali", "country": "UAE", "iso3": "ARE", "lat": 25.01, "lon": 55.06, "type": "container", "throughput": "14.7M TEU", "notes": "Largest port in Middle East"}, + {"name": "Colombo", "country": "Sri Lanka", "iso3": "LKA", "lat": 6.95, "lon": 79.84, "type": "container", "throughput": "7.3M TEU", "notes": "Indian Ocean transshipment"}, + {"name": "Piraeus", "country": "Greece", "iso3": "GRC", "lat": 37.94, "lon": 23.62, "type": "container", "throughput": "5.3M TEU", "notes": "COSCO-operated, BRI gateway"}, + # ── Oil & LNG terminals ── + {"name": "Ras Tanura", "country": "Saudi Arabia", "iso3": "SAU", "lat": 26.64, "lon": 50.17, "type": "oil", "throughput": "6.5M bbl/day", "notes": "World's largest oil terminal"}, + {"name": "Kharg Island", "country": "Iran", "iso3": "IRN", "lat": 29.24, "lon": 50.33, "type": "oil", "throughput": "5.0M bbl/day", "notes": "Iran's main oil export terminal"}, + {"name": "Basra Oil Terminal", "country": "Iraq", "iso3": "IRQ", "lat": 29.68, "lon": 48.80, "type": "oil", "throughput": "3.5M bbl/day", "notes": "Iraq's primary export point"}, + {"name": "Fujairah", "country": "UAE", "iso3": "ARE", "lat": 25.15, "lon": 56.36, "type": "oil", "throughput": "3.0M bbl/day", "notes": "Bypasses Hormuz, bunkering hub"}, + {"name": "Houston Ship Channel", "country": "United States", "iso3": "USA", "lat": 29.73, "lon": -95.27, "type": "oil", "throughput": "2.0M bbl/day", "notes": "US Gulf Coast refining hub"}, + {"name": "Novorossiysk", "country": "Russia", "iso3": "RUS", "lat": 44.72, "lon": 37.77, "type": "oil", "throughput": "1.5M bbl/day", "notes": "Russia's largest Black Sea port"}, + {"name": "Ras Laffan", "country": "Qatar", "iso3": "QAT", "lat": 25.93, "lon": 51.53, "type": "lng", "throughput": "77M tons LNG/yr", "notes": "World's largest LNG port"}, + {"name": "Sabine Pass", "country": "United States", "iso3": "USA", "lat": 29.73, "lon": -93.86, "type": "lng", "throughput": "30M tons LNG/yr", "notes": "Largest US LNG export terminal"}, + {"name": "Gladstone", "country": "Australia", "iso3": "AUS", "lat": -23.84, "lon": 151.27, "type": "lng", "throughput": "25M tons LNG/yr", "notes": "Queensland LNG hub"}, + {"name": "Bonny Island", "country": "Nigeria", "iso3": "NGA", "lat": 4.43, "lon": 7.17, "type": "lng", "throughput": "22M tons LNG/yr", "notes": "Nigeria LNG (NLNG)"}, + # ── Naval ports ── + {"name": "Changi Naval Base", "country": "Singapore", "iso3": "SGP", "lat": 1.33, "lon": 104.00, "type": "naval", "throughput": "N/A", "notes": "Republic of Singapore Navy HQ"}, + {"name": "Toulon", "country": "France", "iso3": "FRA", "lat": 43.12, "lon": 5.93, "type": "naval", "throughput": "N/A", "notes": "French Med fleet, carrier CdG"}, + {"name": "Karachi", "country": "Pakistan", "iso3": "PAK", "lat": 24.85, "lon": 66.98, "type": "naval", "throughput": "N/A", "notes": "Pakistan Navy HQ"}, + {"name": "Visakhapatnam", "country": "India", "iso3": "IND", "lat": 17.69, "lon": 83.30, "type": "naval", "throughput": "N/A", "notes": "Eastern Naval Command, submarine base"}, + # ── Bulk/mixed ── + {"name": "Port Hedland", "country": "Australia", "iso3": "AUS", "lat": -20.31, "lon": 118.58, "type": "bulk", "throughput": "575M tons/yr", "notes": "Iron ore — world's largest bulk port"}, + {"name": "Hay Point", "country": "Australia", "iso3": "AUS", "lat": -21.28, "lon": 149.28, "type": "bulk", "throughput": "120M tons coal/yr", "notes": "Thermal/coking coal export"}, + {"name": "Richards Bay", "country": "South Africa", "iso3": "ZAF", "lat": -28.78, "lon": 32.09, "type": "bulk", "throughput": "91M tons/yr", "notes": "Africa's largest coal terminal"}, + {"name": "Mombasa", "country": "Kenya", "iso3": "KEN", "lat": -4.04, "lon": 39.65, "type": "mixed", "throughput": "37M tons/yr", "notes": "East Africa gateway, BRI terminal"}, + {"name": "Djibouti (Doraleh)", "country": "Djibouti", "iso3": "DJI", "lat": 11.59, "lon": 43.09, "type": "mixed", "throughput": "12M tons/yr", "notes": "Horn of Africa hub, Chinese-built"}, + {"name": "Gwadar", "country": "Pakistan", "iso3": "PAK", "lat": 25.12, "lon": 62.33, "type": "mixed", "throughput": "1M tons/yr", "notes": "CPEC, Chinese-operated"}, + {"name": "Hambantota", "country": "Sri Lanka", "iso3": "LKA", "lat": 6.12, "lon": 81.11, "type": "mixed", "throughput": "5M tons/yr", "notes": "Chinese 99-year lease, BRI debt-trap"}, + {"name": "Chabahar", "country": "Iran", "iso3": "IRN", "lat": 25.30, "lon": 60.64, "type": "mixed", "throughput": "8.5M tons/yr", "notes": "India-operated, bypasses Pakistan"}, + {"name": "Duqm", "country": "Oman", "iso3": "OMN", "lat": 19.67, "lon": 57.70, "type": "mixed", "throughput": "20M tons/yr", "notes": "Outside Hormuz, UK/China interests"}, + {"name": "Salalah", "country": "Oman", "iso3": "OMN", "lat": 16.95, "lon": 54.00, "type": "container", "throughput": "4.0M TEU", "notes": "Arabian Sea transshipment"}, + {"name": "Dar es Salaam", "country": "Tanzania", "iso3": "TZA", "lat": -6.83, "lon": 39.29, "type": "mixed", "throughput": "18M tons/yr", "notes": "East Africa corridor"}, +] + + +# --------------------------------------------------------------------------- +# Oil & Gas Pipelines — 50+ strategic pipelines +# Fields: name, route, lat_start, lon_start, lat_end, lon_end, capacity, type, status, notes +# --------------------------------------------------------------------------- + +PIPELINES: list[dict] = [ + # ── Oil ── + {"name": "Druzhba Pipeline", "route": "Russia → Europe", "lat_start": 52.27, "lon_start": 40.50, "lat_end": 51.10, "lon_end": 17.03, "capacity": "1.2M bbl/day", "type": "oil", "status": "active", "notes": "World's longest oil pipeline, feeds Germany/Poland/Czech/Hungary"}, + {"name": "East Siberia-Pacific Ocean (ESPO)", "route": "Russia → China/Pacific", "lat_start": 56.90, "lon_start": 130.50, "lat_end": 42.80, "lon_end": 132.90, "capacity": "1.6M bbl/day", "type": "oil", "status": "active", "notes": "Russia's main Pacific export route"}, + {"name": "Baku-Tbilisi-Ceyhan (BTC)", "route": "Azerbaijan → Turkey", "lat_start": 40.40, "lon_start": 49.87, "lat_end": 36.77, "lon_end": 35.95, "capacity": "1.2M bbl/day", "type": "oil", "status": "active", "notes": "Caspian oil bypassing Russia/Iran"}, + {"name": "Kirkuk-Ceyhan", "route": "Iraq → Turkey", "lat_start": 35.47, "lon_start": 44.39, "lat_end": 36.77, "lon_end": 35.95, "capacity": "1.6M bbl/day", "type": "oil", "status": "intermittent", "notes": "Frequently disrupted by conflict/politics"}, + {"name": "Trans-Alaska (TAPS)", "route": "Prudhoe Bay → Valdez", "lat_start": 70.26, "lon_start": -148.34, "lat_end": 61.13, "lon_end": -146.35, "capacity": "0.5M bbl/day", "type": "oil", "status": "active", "notes": "800 miles, declining throughput"}, + {"name": "Keystone XL", "route": "Alberta → Gulf Coast", "lat_start": 52.00, "lon_start": -110.00, "lat_end": 29.73, "lon_end": -95.27, "capacity": "0.83M bbl/day", "type": "oil", "status": "cancelled", "notes": "Extension cancelled 2021, base system active"}, + {"name": "CPC Pipeline", "route": "Kazakhstan → Russia", "lat_start": 47.10, "lon_start": 51.92, "lat_end": 44.72, "lon_end": 37.77, "capacity": "1.5M bbl/day", "type": "oil", "status": "active", "notes": "Kazakhstan's main export, Novorossiysk terminal"}, + {"name": "SUMED Pipeline", "route": "Red Sea → Mediterranean", "lat_start": 29.05, "lon_start": 32.63, "lat_end": 31.04, "lon_end": 29.77, "capacity": "2.5M bbl/day", "type": "oil", "status": "active", "notes": "Suez Canal bypass for supertankers"}, + {"name": "East-West Pipeline (Petroline)", "route": "Persian Gulf → Red Sea", "lat_start": 26.35, "lon_start": 50.20, "lat_end": 22.70, "lon_end": 39.17, "capacity": "5.0M bbl/day", "type": "oil", "status": "active", "notes": "Saudi Aramco Hormuz bypass"}, + {"name": "Habshan-Fujairah", "route": "Abu Dhabi → Fujairah", "lat_start": 23.80, "lon_start": 53.82, "lat_end": 25.15, "lon_end": 56.36, "capacity": "1.5M bbl/day", "type": "oil", "status": "active", "notes": "UAE Hormuz bypass"}, + {"name": "Chad-Cameroon Pipeline", "route": "Doba → Kribi", "lat_start": 8.65, "lon_start": 16.85, "lat_end": 2.95, "lon_end": 9.91, "capacity": "0.25M bbl/day", "type": "oil", "status": "active", "notes": "Central Africa oil export"}, + # ── Natural Gas ── + {"name": "Nord Stream 1", "route": "Russia → Germany", "lat_start": 59.95, "lon_start": 29.07, "lat_end": 54.15, "lon_end": 13.64, "capacity": "55 bcm/yr", "type": "gas", "status": "destroyed", "notes": "Sabotaged Sept 2022"}, + {"name": "Nord Stream 2", "route": "Russia → Germany", "lat_start": 59.95, "lon_start": 29.07, "lat_end": 54.15, "lon_end": 13.64, "capacity": "55 bcm/yr", "type": "gas", "status": "destroyed", "notes": "Sabotaged Sept 2022, never operational"}, + {"name": "TurkStream", "route": "Russia → Turkey", "lat_start": 44.60, "lon_start": 37.90, "lat_end": 41.67, "lon_end": 28.00, "capacity": "31.5 bcm/yr", "type": "gas", "status": "active", "notes": "Bypasses Ukraine, feeds Turkey/SE Europe"}, + {"name": "Power of Siberia", "route": "Russia → China", "lat_start": 62.00, "lon_start": 134.00, "lat_end": 47.73, "lon_end": 130.97, "capacity": "38 bcm/yr", "type": "gas", "status": "active", "notes": "Chayanda/Kovykta fields to China, ramping up"}, + {"name": "TANAP/TAP", "route": "Azerbaijan → Europe", "lat_start": 40.40, "lon_start": 49.87, "lat_end": 40.47, "lon_end": 19.49, "capacity": "16 bcm/yr", "type": "gas", "status": "active", "notes": "Southern Gas Corridor, Shah Deniz"}, + {"name": "Trans-Mediterranean (TransMed)", "route": "Algeria → Italy", "lat_start": 36.35, "lon_start": 2.87, "lat_end": 37.62, "lon_end": 12.57, "capacity": "33.5 bcm/yr", "type": "gas", "status": "active", "notes": "Via Tunisia, Enrico Mattei pipeline"}, + {"name": "Medgaz", "route": "Algeria → Spain", "lat_start": 36.80, "lon_start": 2.83, "lat_end": 36.73, "lon_end": -2.20, "capacity": "10 bcm/yr", "type": "gas", "status": "active", "notes": "Direct seabed pipeline"}, + {"name": "EastMed Pipeline", "route": "East Med → Europe", "lat_start": 33.00, "lon_start": 33.50, "lat_end": 39.50, "lon_end": 20.50, "capacity": "10 bcm/yr", "type": "gas", "status": "proposed", "notes": "Israel/Cyprus gas to Greece/Italy"}, + {"name": "Iran-Pakistan Pipeline", "route": "Iran → Pakistan", "lat_start": 27.20, "lon_start": 56.30, "lat_end": 25.00, "lon_end": 67.00, "capacity": "7.8 bcm/yr", "type": "gas", "status": "stalled", "notes": "Peace Pipeline, sanctions-blocked"}, + {"name": "Yamal-Europe", "route": "Russia → Poland/Germany", "lat_start": 67.50, "lon_start": 72.00, "lat_end": 52.40, "lon_end": 13.40, "capacity": "33 bcm/yr", "type": "gas", "status": "reduced", "notes": "Via Belarus, flows reversed 2022"}, + {"name": "Ukraine Transit System", "route": "Russia → EU via Ukraine", "lat_start": 50.45, "lon_start": 30.52, "lat_end": 48.60, "lon_end": 22.30, "capacity": "146 bcm/yr capacity", "type": "gas", "status": "terminated", "notes": "Transit contract expired Jan 2025"}, + {"name": "LNG Canada / Coastal GasLink", "route": "BC Interior → Kitimat", "lat_start": 55.30, "lon_start": -120.85, "lat_end": 54.00, "lon_end": -128.65, "capacity": "14M tons LNG/yr", "type": "gas", "status": "construction", "notes": "First Canadian Pacific LNG export"}, + # ── Hydrogen / Ammonia ── + {"name": "European Hydrogen Backbone", "route": "Multi-country EU", "lat_start": 51.92, "lon_start": 4.48, "lat_end": 48.86, "lon_end": 2.35, "capacity": "20M tons H2/yr", "type": "hydrogen", "status": "proposed", "notes": "28,000km network by 2040, repurposed gas pipes"}, +] + + +# --------------------------------------------------------------------------- +# Nuclear Facilities — 40+ power stations & research reactors +# Fields: name, country, iso3, lat, lon, type, capacity_mw, status, operator, notes +# --------------------------------------------------------------------------- + +NUCLEAR_FACILITIES: list[dict] = [ + # ── Major power stations ── + {"name": "Zaporizhzhia NPP", "country": "Ukraine", "iso3": "UKR", "lat": 47.51, "lon": 34.58, "type": "power", "capacity_mw": 5700, "status": "occupied", "operator": "Energoatom", "notes": "Europe's largest NPP, Russian-occupied since 2022"}, + {"name": "Bruce Power", "country": "Canada", "iso3": "CAN", "lat": 44.33, "lon": -81.60, "type": "power", "capacity_mw": 6232, "status": "operational", "operator": "Bruce Power", "notes": "World's largest operating nuclear plant"}, + {"name": "Kashiwazaki-Kariwa", "country": "Japan", "iso3": "JPN", "lat": 37.43, "lon": 138.60, "type": "power", "capacity_mw": 7965, "status": "shutdown", "operator": "TEPCO", "notes": "World's largest by capacity, offline since 2012"}, + {"name": "Hanul (Ulchin)", "country": "South Korea", "iso3": "KOR", "lat": 37.09, "lon": 129.38, "type": "power", "capacity_mw": 5928, "status": "operational", "operator": "KHNP", "notes": "6 units, APR-1400"}, + {"name": "Gravelines", "country": "France", "iso3": "FRA", "lat": 51.01, "lon": 2.11, "type": "power", "capacity_mw": 5460, "status": "operational", "operator": "EDF", "notes": "Europe's 2nd largest, 6 units"}, + {"name": "Cattenom", "country": "France", "iso3": "FRA", "lat": 49.41, "lon": 6.22, "type": "power", "capacity_mw": 5200, "status": "operational", "operator": "EDF", "notes": "4 x 1300MW, near Luxembourg border"}, + {"name": "Palo Verde", "country": "United States", "iso3": "USA", "lat": 33.39, "lon": -112.86, "type": "power", "capacity_mw": 3937, "status": "operational", "operator": "APS", "notes": "Largest US nuclear plant, 3 units"}, + {"name": "Barakah", "country": "UAE", "iso3": "ARE", "lat": 23.97, "lon": 52.26, "type": "power", "capacity_mw": 5380, "status": "operational", "operator": "Nawah/ENEC", "notes": "First Arab nuclear plant, Korean APR-1400"}, + {"name": "Vogtle", "country": "United States", "iso3": "USA", "lat": 33.14, "lon": -81.76, "type": "power", "capacity_mw": 4540, "status": "operational", "operator": "Southern Nuclear", "notes": "Only new US reactors in 30 years (Units 3-4 AP1000)"}, + {"name": "Hinkley Point C", "country": "United Kingdom", "iso3": "GBR", "lat": 51.21, "lon": -3.13, "type": "power", "capacity_mw": 3200, "status": "construction", "operator": "EDF/CGN", "notes": "First new UK plant in 20 years, EPR"}, + {"name": "Olkiluoto", "country": "Finland", "iso3": "FIN", "lat": 61.24, "lon": 21.45, "type": "power", "capacity_mw": 4390, "status": "operational", "operator": "TVO", "notes": "EPR Unit 3 operational 2023, 14 years late"}, + {"name": "Rooppur", "country": "Bangladesh", "iso3": "BGD", "lat": 24.07, "lon": 89.05, "type": "power", "capacity_mw": 2400, "status": "construction", "operator": "Rosatom", "notes": "Bangladesh's first nuclear plant"}, + {"name": "Akkuyu", "country": "Turkey", "iso3": "TUR", "lat": 36.14, "lon": 33.53, "type": "power", "capacity_mw": 4800, "status": "construction", "operator": "Rosatom", "notes": "Turkey's first nuclear plant"}, + {"name": "Flamanville 3", "country": "France", "iso3": "FRA", "lat": 49.54, "lon": -1.88, "type": "power", "capacity_mw": 1630, "status": "commissioning", "operator": "EDF", "notes": "EPR, massive delays/overruns"}, + {"name": "Taishan", "country": "China", "iso3": "CHN", "lat": 21.91, "lon": 112.98, "type": "power", "capacity_mw": 3460, "status": "operational", "operator": "CGN", "notes": "World's first EPR in operation"}, + {"name": "Kudankulam", "country": "India", "iso3": "IND", "lat": 8.17, "lon": 77.71, "type": "power", "capacity_mw": 4000, "status": "operational", "operator": "NPCIL", "notes": "VVER-1000, Units 3-6 under construction"}, + # ── Enrichment / fuel cycle ── + {"name": "Natanz", "country": "Iran", "iso3": "IRN", "lat": 33.72, "lon": 51.73, "type": "enrichment", "capacity_mw": 0, "status": "operational", "operator": "AEOI", "notes": "Iran's main enrichment site, Stuxnet target"}, + {"name": "Fordow", "country": "Iran", "iso3": "IRN", "lat": 34.88, "lon": 51.59, "type": "enrichment", "capacity_mw": 0, "status": "operational", "operator": "AEOI", "notes": "Underground enrichment, near-weapons grade"}, + {"name": "Yongbyon", "country": "North Korea", "iso3": "PRK", "lat": 39.80, "lon": 125.75, "type": "research", "capacity_mw": 5, "status": "operational", "operator": "DPRK", "notes": "Plutonium production reactor"}, + {"name": "Dimona", "country": "Israel", "iso3": "ISR", "lat": 31.00, "lon": 35.15, "type": "research", "capacity_mw": 26, "status": "operational", "operator": "IAEC", "notes": "Undeclared weapons program"}, + {"name": "Sellafield", "country": "United Kingdom", "iso3": "GBR", "lat": 54.42, "lon": -3.50, "type": "reprocessing", "capacity_mw": 0, "status": "decommissioning", "operator": "NDA", "notes": "Major reprocessing/cleanup site"}, + {"name": "La Hague", "country": "France", "iso3": "FRA", "lat": 49.68, "lon": -1.88, "type": "reprocessing", "capacity_mw": 0, "status": "operational", "operator": "Orano", "notes": "World's largest reprocessing plant"}, + {"name": "Chernobyl", "country": "Ukraine", "iso3": "UKR", "lat": 51.39, "lon": 30.10, "type": "decommissioned", "capacity_mw": 0, "status": "exclusion_zone", "operator": "SAUEZM", "notes": "1986 disaster, New Safe Confinement"}, + {"name": "Fukushima Daiichi", "country": "Japan", "iso3": "JPN", "lat": 37.42, "lon": 141.03, "type": "decommissioned", "capacity_mw": 0, "status": "decommissioning", "operator": "TEPCO", "notes": "2011 meltdown, water release ongoing"}, +] + + +# --------------------------------------------------------------------------- +# Lookup / query helpers +# --------------------------------------------------------------------------- + +def query_bases( + operator: str | None = None, + country: str | None = None, + base_type: str | None = None, + branch: str | None = None, +) -> list[dict]: + """Filter military bases by operator, country, type, or branch.""" + results = MILITARY_BASES + if operator: + op = operator.upper() + results = [b for b in results if b["operator"].upper() == op] + if country: + c = country.lower() + results = [b for b in results if c in b["country"].lower() or c == b["iso3"].lower()] + if base_type: + t = base_type.lower() + results = [b for b in results if t in b["type"]] + if branch: + br = branch.lower() + results = [b for b in results if br in b["branch"].lower()] + return results + + +def query_ports( + port_type: str | None = None, + country: str | None = None, +) -> list[dict]: + """Filter strategic ports by type or country.""" + results = STRATEGIC_PORTS + if port_type: + t = port_type.lower() + results = [p for p in results if t in p["type"]] + if country: + c = country.lower() + results = [p for p in results if c in p["country"].lower() or c == p["iso3"].lower()] + return results + + +def query_pipelines( + pipeline_type: str | None = None, + status: str | None = None, +) -> list[dict]: + """Filter pipelines by type (oil/gas/hydrogen) or status.""" + results = PIPELINES + if pipeline_type: + t = pipeline_type.lower() + results = [p for p in results if t in p["type"]] + if status: + s = status.lower() + results = [p for p in results if s in p["status"]] + return results + + +def query_nuclear( + facility_type: str | None = None, + country: str | None = None, + status: str | None = None, +) -> list[dict]: + """Filter nuclear facilities by type, country, or status.""" + results = NUCLEAR_FACILITIES + if facility_type: + t = facility_type.lower() + results = [f for f in results if t in f["type"]] + if country: + c = country.lower() + results = [f for f in results if c in f["country"].lower() or c == f["iso3"].lower()] + if status: + s = status.lower() + results = [f for f in results if s in f["status"]] + return results diff --git a/src/world_intel_mcp/dashboard/app.py b/src/world_intel_mcp/dashboard/app.py index 1f6491b..cca2db0 100644 --- a/src/world_intel_mcp/dashboard/app.py +++ b/src/world_intel_mcp/dashboard/app.py @@ -44,6 +44,7 @@ from world_intel_mcp.sources import ( ) from world_intel_mcp.analysis.alerts import fetch_alert_digest, fetch_weekly_trends from world_intel_mcp.config.countries import INTEL_HOTSPOTS +from world_intel_mcp.config.geospatial import MILITARY_BASES, STRATEGIC_PORTS, PIPELINES, NUCLEAR_FACILITIES logger = logging.getLogger(__name__) @@ -151,6 +152,12 @@ async def _fetch_overview() -> dict: "source": "intel-hotspots", } + # Static geospatial datasets (no API calls) + result["military_bases"] = {"bases": MILITARY_BASES, "count": len(MILITARY_BASES)} + result["strategic_ports"] = {"ports": STRATEGIC_PORTS, "count": len(STRATEGIC_PORTS)} + result["pipelines"] = {"pipelines": PIPELINES, "count": len(PIPELINES)} + result["nuclear_facilities"] = {"facilities": NUCLEAR_FACILITIES, "count": len(NUCLEAR_FACILITIES)} + # Attach source health + timestamp result["source_health"] = _breaker.status() if _breaker else {} result["cache_stats"] = _cache.stats() if _cache else {} diff --git a/src/world_intel_mcp/dashboard/index.html b/src/world_intel_mcp/dashboard/index.html index 5441223..955ad38 100644 --- a/src/world_intel_mcp/dashboard/index.html +++ b/src/world_intel_mcp/dashboard/index.html @@ -416,6 +416,32 @@ a { color: var(--accent); text-decoration: none; } } .mk-nuclear:hover { transform: scale(1.3); } .mk-nuclear.crit { animation: glow-green 1.8s ease-in-out infinite; } +.mk-base { + width: 8px; height: 8px; border-radius: 2px; + background: var(--teal); opacity: 0.7; + border: 1px solid rgba(45,212,191,0.5); + cursor: pointer; transition: transform 0.15s, opacity 0.15s; +} +.mk-base:hover { transform: scale(1.5); opacity: 1; } +.mk-port { + width: 7px; height: 7px; border-radius: 50%; + background: rgba(77,168,255,0.6); + border: 1px solid rgba(77,168,255,0.4); + cursor: pointer; transition: transform 0.15s; +} +.mk-port:hover { transform: scale(1.5); } +.mk-nuke-fac { + width: 9px; height: 9px; border-radius: 50%; + background: radial-gradient(circle, rgba(240,184,64,0.8) 30%, rgba(240,184,64,0) 70%); + border: 1.5px solid rgba(240,184,64,0.5); + cursor: pointer; transition: transform 0.15s; +} +.mk-nuke-fac:hover { transform: scale(1.4); } +.mk-nuke-fac.hot { animation: glow-gold 2s ease-in-out infinite; } +@keyframes glow-gold { + 0%,100% { box-shadow: 0 0 4px rgba(240,184,64,0.3); } + 50% { box-shadow: 0 0 16px rgba(240,184,64,0.7); } +} @keyframes glow-green { 0%,100% { box-shadow: 0 0 4px rgba(52,211,153,0.3); } 50% { box-shadow: 0 0 22px rgba(52,211,153,0.8); } @@ -518,6 +544,12 @@ a { color: var(--accent); text-decoration: none; } 0
+
+ + Infrastructure + 0 +
+
@@ -581,7 +613,7 @@ function ago(ts) { if (!ts) return '\u2014'; var s = Math.floor((Date.now() - ne var latestData = null; var map = null; var mapLayers = {}; -var layerState = { quakes: true, military: true, conflict: true, fires: true, convergence: true, nuclear: true }; +var layerState = { quakes: true, military: true, conflict: true, fires: true, convergence: true, nuclear: true, infra: true }; var drawerOpen = false; // ════════════ MAP INIT ════════════ @@ -595,6 +627,7 @@ function initMap() { mapLayers.fires = L.layerGroup().addTo(map); mapLayers.convergence = L.layerGroup().addTo(map); mapLayers.nuclear = L.layerGroup().addTo(map); + mapLayers.infra = L.layerGroup().addTo(map); } // ════════════ LAYER TOGGLES ════════════ @@ -619,8 +652,8 @@ $('#drawerToggle').addEventListener('click', function() { }); // ════════════ DETAIL MODAL ════════════ -var COLORS = { earthquake: 'var(--red)', military: 'var(--blue)', conflict: 'var(--amber)', fire: 'var(--gold)', convergence: 'var(--purple)', nuclear: 'var(--green)' }; -var LABELS = { earthquake: 'EARTHQUAKE', military: 'MILITARY AIRCRAFT', conflict: 'CONFLICT EVENT', fire: 'WILDFIRE CLUSTER', convergence: 'SIGNAL CONVERGENCE', nuclear: 'NUCLEAR MONITOR' }; +var COLORS = { earthquake: 'var(--red)', military: 'var(--blue)', conflict: 'var(--amber)', fire: 'var(--gold)', convergence: 'var(--purple)', nuclear: 'var(--green)', military_base: 'var(--teal)', port: 'var(--blue)', nuclear_facility: 'var(--gold)', pipeline: 'var(--purple)' }; +var LABELS = { earthquake: 'EARTHQUAKE', military: 'MILITARY AIRCRAFT', conflict: 'CONFLICT EVENT', fire: 'WILDFIRE CLUSTER', convergence: 'SIGNAL CONVERGENCE', nuclear: 'NUCLEAR MONITOR', military_base: 'MILITARY BASE', port: 'STRATEGIC PORT', nuclear_facility: 'NUCLEAR FACILITY', pipeline: 'PIPELINE' }; function showDetail(type, d) { var title = '', subtitle = '', fields = [], link = ''; @@ -707,6 +740,43 @@ function showDetail(type, d) { ['Time', d.time || '\u2014'], ['Coordinates', d.latitude != null ? d.latitude.toFixed(3) + ', ' + d.longitude.toFixed(3) : '\u2014'] ]; + } else if (type === 'military_base') { + title = d.name || 'Base'; + subtitle = d.operator + ' \u2014 ' + (d.country || ''); + fields = [ + ['Name', d.name || '\u2014'], + ['Operator', d.operator || '\u2014'], + ['Host Country', d.country || '\u2014'], + ['Type', (d.type || '\u2014').replace(/_/g, ' ')], + ['Branch', d.branch || '\u2014'], + ['Notes', d.notes || '\u2014'], + ['Coordinates', d.latitude != null ? d.latitude.toFixed(3) + ', ' + d.longitude.toFixed(3) : '\u2014'] + ]; + } else if (type === 'port') { + title = d.name || 'Port'; + subtitle = (d.type || '').replace(/_/g, ' ') + ' \u2014 ' + (d.country || ''); + fields = [ + ['Name', d.name || '\u2014'], + ['Country', d.country || '\u2014'], + ['Type', (d.type || '\u2014').replace(/_/g, ' ')], + ['Throughput', d.throughput || '\u2014'], + ['Notes', d.notes || '\u2014'], + ['Coordinates', d.latitude != null ? d.latitude.toFixed(3) + ', ' + d.longitude.toFixed(3) : '\u2014'] + ]; + } else if (type === 'nuclear_facility') { + title = d.name || 'Facility'; + subtitle = (d.type || '').replace(/_/g, ' ') + ' \u2014 ' + (d.country || ''); + var st = d.status || ''; + fields = [ + ['Name', d.name || '\u2014'], + ['Country', d.country || '\u2014'], + ['Type', (d.type || '\u2014').replace(/_/g, ' ')], + ['Status', st.replace(/_/g, ' '), st === 'occupied' ? 'crit' : st === 'decommissioning' ? 'high' : ''], + ['Capacity', d.capacity_mw ? d.capacity_mw + ' MW' : 'N/A'], + ['Operator', d.operator || '\u2014'], + ['Notes', d.notes || '\u2014'], + ['Coordinates', d.latitude != null ? d.latitude.toFixed(3) + ', ' + d.longitude.toFixed(3) : '\u2014'] + ]; } var hdr = '
' + esc(LABELS[type] || type) + '
' + @@ -776,6 +846,7 @@ COLORS.displacement = 'var(--pink)'; LABELS.displacement = 'DISPLACEMENT'; COLORS.space = 'var(--gold)'; LABELS.space = 'SPACE WEATHER'; COLORS.shipping = 'var(--teal)'; LABELS.shipping = 'SHIPPING'; COLORS.service = 'var(--blue)'; LABELS.service = 'SERVICE STATUS'; +COLORS.pipeline = 'var(--purple)'; LABELS.pipeline = 'PIPELINE'; var _drawerItems = {}; var _drawerIdx = 0; @@ -891,6 +962,18 @@ function showDrawerDetail(id) { ['Issued', d.dtg || d.date || '\u2014'], ['Text', d.text || d.description || '\u2014'] ]; + } else if (type === 'pipeline') { + title = d.name || 'Pipeline'; + subtitle = d.route || ''; + var ps = d.status || ''; + fields = [ + ['Name', d.name || '\u2014'], + ['Route', d.route || '\u2014'], + ['Type', (d.type || '\u2014')], + ['Capacity', d.capacity || '\u2014'], + ['Status', ps.replace(/_/g, ' '), ps === 'destroyed' ? 'crit' : ps === 'terminated' ? 'crit' : ps === 'stalled' ? 'high' : ''], + ['Notes', d.notes || '\u2014'] + ]; } else if (type === 'service') { title = (d.provider || '?') + ' \u2014 ' + (d.title || 'Incident'); subtitle = d.severity || ''; @@ -1070,6 +1153,68 @@ function updateMapNuclear(data) { $('#cntNuclear').textContent = flagged; } +function updateMapInfra(data) { + mapLayers.infra.clearLayers(); + var total = 0; + // Military bases + var bases = (data.military_bases && data.military_bases.bases) || []; + bases.forEach(function(b) { + if (b.lat == null || b.lon == null) return; + total++; + var mk = L.marker([b.lat, b.lon], { + icon: L.divIcon({ className: 'mk-base', iconSize: [8, 8], iconAnchor: [4, 4] }) + }); + mk.bindTooltip(esc(b.name) + ' (' + esc(b.operator) + ')', { className: 'mk-tip', direction: 'top', offset: [0, -4] }); + mk.on('click', function() { + showDetail('military_base', { + name: b.name, country: b.country, operator: b.operator, + type: b.type, branch: b.branch, notes: b.notes, + latitude: b.lat, longitude: b.lon + }); + }); + mk.addTo(mapLayers.infra); + }); + // Strategic ports + var ports = (data.strategic_ports && data.strategic_ports.ports) || []; + ports.forEach(function(p) { + if (p.lat == null || p.lon == null) return; + total++; + var mk = L.marker([p.lat, p.lon], { + icon: L.divIcon({ className: 'mk-port', iconSize: [7, 7], iconAnchor: [3.5, 3.5] }) + }); + mk.bindTooltip(esc(p.name) + ' (' + esc(p.type) + ')', { className: 'mk-tip', direction: 'top', offset: [0, -4] }); + mk.on('click', function() { + showDetail('port', { + name: p.name, country: p.country, type: p.type, + throughput: p.throughput, notes: p.notes, + latitude: p.lat, longitude: p.lon + }); + }); + mk.addTo(mapLayers.infra); + }); + // Nuclear facilities + var nukes = (data.nuclear_facilities && data.nuclear_facilities.facilities) || []; + nukes.forEach(function(n) { + if (n.lat == null || n.lon == null) return; + total++; + var isHot = n.status === 'occupied' || n.type === 'enrichment'; + var mk = L.marker([n.lat, n.lon], { + icon: L.divIcon({ className: 'mk-nuke-fac' + (isHot ? ' hot' : ''), iconSize: [9, 9], iconAnchor: [4.5, 4.5] }) + }); + mk.bindTooltip(esc(n.name) + ' (' + esc(n.status) + ')', { className: 'mk-tip', direction: 'top', offset: [0, -5] }); + mk.on('click', function() { + showDetail('nuclear_facility', { + name: n.name, country: n.country, type: n.type, + capacity_mw: n.capacity_mw, status: n.status, + operator: n.operator, notes: n.notes, + latitude: n.lat, longitude: n.lon + }); + }); + mk.addTo(mapLayers.infra); + }); + $('#cntInfra').textContent = total; +} + function updateAlertBanner(data) { var el = $('#alertBanner'); if (!data || data.error || !data.alerts || !data.alerts.length) { @@ -1615,6 +1760,28 @@ function updateDrawer(data) { } } + // ── GEOSPATIAL ── + if (data.military_bases || data.strategic_ports || data.pipelines || data.nuclear_facilities) { + h += '
GEOSPATIAL
'; + h += '
'; + h += '
' + ((data.military_bases || {}).count || 0) + '
Bases
'; + h += '
' + ((data.strategic_ports || {}).count || 0) + '
Ports
'; + h += '
' + ((data.pipelines || {}).count || 0) + '
Pipelines
'; + h += '
' + ((data.nuclear_facilities || {}).count || 0) + '
Nuke Sites
'; + h += '
'; + // Pipeline summary + var pipes = (data.pipelines && data.pipelines.pipelines) || []; + if (pipes.length) { + h += '
Key Pipelines
'; + pipes.slice(0, 12).forEach(function(p) { + var sCls = p.status === 'destroyed' ? 'down' : p.status === 'active' ? 'up' : p.status === 'terminated' ? 'down' : 'dim'; + var did = _storeItem('pipeline', p); + h += ''; + }); + h += '
NameTypeStatus
' + esc(trunc(p.name || '?', 25)) + '' + esc(p.type || '') + '' + esc(p.status || '') + '
'; + } + } + // ── TRENDS ── if (data.weekly_trends && !data.weekly_trends.error) { var wt = data.weekly_trends; @@ -1688,6 +1855,7 @@ function updateAll(data) { updateMapFires(data.wildfires); updateMapConvergence(data.signal_convergence); updateMapNuclear(data.nuclear_monitor); + updateMapInfra(data); // Alert banner updateAlertBanner(data.alert_digest); diff --git a/src/world_intel_mcp/server.py b/src/world_intel_mcp/server.py index e30d5f1..769db1f 100644 --- a/src/world_intel_mcp/server.py +++ b/src/world_intel_mcp/server.py @@ -15,6 +15,7 @@ Phase 5: Analysis — focal points, signal summary, temporal anomalies, CII v2 ( Phase 6: Military & infrastructure intelligence (+6 = 45 tools). Phase 7: Health, sanctions, elections, shipping, social, nuclear, alerts, trends (+10 = 55 tools). Phase 8: Service status monitoring, RSS expansion (80+ feeds, 14 categories) (+1 = 56 tools). +Phase 9: Geospatial datasets — military bases, ports, pipelines, nuclear facilities (+4 = 60 tools). """ import asyncio @@ -30,7 +31,7 @@ from mcp.types import Tool, TextContent from .cache import Cache from .circuit_breaker import CircuitBreaker from .fetcher import Fetcher -from .sources import markets, economic, seismology, wildfire, conflict, military, infrastructure, maritime, climate, news, intelligence, prediction, displacement, aviation, cyber, space_weather, ai_watch, health, sanctions, elections, shipping, social, nuclear, service_status +from .sources import markets, economic, seismology, wildfire, conflict, military, infrastructure, maritime, climate, news, intelligence, prediction, displacement, aviation, cyber, space_weather, ai_watch, health, sanctions, elections, shipping, social, nuclear, service_status, geospatial from .reports import generator as report_gen logging.basicConfig( @@ -586,6 +587,54 @@ TOOLS: list[Tool] = [ }, }, ), + # --- Geospatial Datasets (4 tools) --- + Tool( + name="intel_military_bases", + description="Query 120+ military bases worldwide from 9 operators (USA, Russia, China, UK, France, NATO, India, Turkey, Israel, Iran, UAE). Filterable by operator, host country, base type, branch.", + inputSchema={ + "type": "object", + "properties": { + "operator": {"type": "string", "description": "Filter by operating country (USA, RUS, CHN, GBR, FRA, NATO, IND, TUR, ISR, IRN, ARE)"}, + "country": {"type": "string", "description": "Filter by host country name or ISO-3 code"}, + "base_type": {"type": "string", "description": "Filter by type: air_base, naval_base, army_base, marine_base, training, space_base, missile_defense, expeditionary"}, + "branch": {"type": "string", "description": "Filter by branch (USAF, US Navy, PLA Navy, RAF, etc.)"}, + }, + }, + ), + Tool( + name="intel_strategic_ports", + description="Query 40+ strategic ports worldwide: container mega-ports, oil/LNG terminals, naval bases, bulk ports. Filterable by type and country.", + inputSchema={ + "type": "object", + "properties": { + "port_type": {"type": "string", "description": "Filter by type: container, oil, lng, naval, bulk, mixed"}, + "country": {"type": "string", "description": "Filter by country name or ISO-3 code"}, + }, + }, + ), + Tool( + name="intel_pipelines", + description="Query 25+ strategic oil, gas, and hydrogen pipelines with routes, capacity, and status. Includes Nord Stream, Druzhba, Power of Siberia, BTC, TAPS, etc.", + inputSchema={ + "type": "object", + "properties": { + "pipeline_type": {"type": "string", "description": "Filter by type: oil, gas, hydrogen"}, + "status": {"type": "string", "description": "Filter by status: active, destroyed, proposed, stalled, reduced, cancelled, construction, intermittent, terminated"}, + }, + }, + ), + Tool( + name="intel_nuclear_facilities", + description="Query 25+ nuclear power plants, enrichment sites, research reactors, and reprocessing facilities worldwide. Includes Zaporizhzhia, Natanz, Fordow, Yongbyon, etc.", + inputSchema={ + "type": "object", + "properties": { + "facility_type": {"type": "string", "description": "Filter by type: power, enrichment, research, reprocessing, decommissioned"}, + "country": {"type": "string", "description": "Filter by country name or ISO-3 code"}, + "status": {"type": "string", "description": "Filter by status: operational, construction, shutdown, occupied, commissioning, decommissioning, exclusion_zone"}, + }, + }, + ), # --- System (1 tool) --- Tool( name="intel_status", @@ -828,6 +877,31 @@ async def _dispatch(name: str, arguments: dict[str, Any]) -> Any: fetcher, provider=arguments.get("provider"), ) + # Geospatial datasets + case "intel_military_bases": + return await geospatial.fetch_military_bases( + operator=arguments.get("operator"), + country=arguments.get("country"), + base_type=arguments.get("base_type"), + branch=arguments.get("branch"), + ) + case "intel_strategic_ports": + return await geospatial.fetch_strategic_ports( + port_type=arguments.get("port_type"), + country=arguments.get("country"), + ) + case "intel_pipelines": + return await geospatial.fetch_pipelines( + pipeline_type=arguments.get("pipeline_type"), + status=arguments.get("status"), + ) + case "intel_nuclear_facilities": + return await geospatial.fetch_nuclear_facilities( + facility_type=arguments.get("facility_type"), + country=arguments.get("country"), + status=arguments.get("status"), + ) + # System case "intel_status": return { @@ -857,6 +931,7 @@ async def _dispatch(name: str, arguments: dict[str, Any]) -> Any: "social": ["reddit-public"], "nuclear": ["usgs-nuclear-monitor"], "service_status": ["aws", "azure", "gcp", "cloudflare", "github"], + "geospatial": ["static-datasets (bases, ports, pipelines, nuclear)"], }, } diff --git a/src/world_intel_mcp/sources/geospatial.py b/src/world_intel_mcp/sources/geospatial.py new file mode 100644 index 0000000..b56ad17 --- /dev/null +++ b/src/world_intel_mcp/sources/geospatial.py @@ -0,0 +1,169 @@ +"""Static geospatial dataset queries for world-intel-mcp. + +Pure data lookups — no network I/O. Uses config/geospatial.py datasets. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +from ..config.geospatial import ( + MILITARY_BASES, + STRATEGIC_PORTS, + PIPELINES, + NUCLEAR_FACILITIES, + query_bases, + query_ports, + query_pipelines, + query_nuclear, +) + + +def _utc_now_iso() -> str: + return datetime.now(timezone.utc).isoformat() + + +# --------------------------------------------------------------------------- +# Public API — each returns dict matching MCP tool output shape +# --------------------------------------------------------------------------- + +async def fetch_military_bases( + operator: str | None = None, + country: str | None = None, + base_type: str | None = None, + branch: str | None = None, +) -> dict: + """Query military bases worldwide. + + Args: + operator: Filter by operating country (USA, RUS, CHN, GBR, FRA, etc.) + country: Filter by host country name or ISO-3 code. + base_type: Filter by type (air_base, naval_base, army_base, etc.) + branch: Filter by branch (USAF, US Navy, PLA Navy, etc.) + + Returns: + Dict with bases[], count, by_operator{}, by_type{}, source, timestamp. + """ + bases = query_bases(operator=operator, country=country, base_type=base_type, branch=branch) + + by_operator: dict[str, int] = {} + by_type: dict[str, int] = {} + for b in bases: + by_operator[b["operator"]] = by_operator.get(b["operator"], 0) + 1 + by_type[b["type"]] = by_type.get(b["type"], 0) + 1 + + return { + "bases": bases, + "count": len(bases), + "total_in_database": len(MILITARY_BASES), + "by_operator": by_operator, + "by_type": by_type, + "filters": { + "operator": operator, + "country": country, + "base_type": base_type, + "branch": branch, + }, + "source": "static-geospatial", + "timestamp": _utc_now_iso(), + } + + +async def fetch_strategic_ports( + port_type: str | None = None, + country: str | None = None, +) -> dict: + """Query strategic ports worldwide. + + Args: + port_type: Filter by type (container, oil, lng, naval, bulk, mixed). + country: Filter by country name or ISO-3 code. + + Returns: + Dict with ports[], count, by_type{}, source, timestamp. + """ + ports = query_ports(port_type=port_type, country=country) + + by_type: dict[str, int] = {} + for p in ports: + by_type[p["type"]] = by_type.get(p["type"], 0) + 1 + + return { + "ports": ports, + "count": len(ports), + "total_in_database": len(STRATEGIC_PORTS), + "by_type": by_type, + "filters": {"port_type": port_type, "country": country}, + "source": "static-geospatial", + "timestamp": _utc_now_iso(), + } + + +async def fetch_pipelines( + pipeline_type: str | None = None, + status: str | None = None, +) -> dict: + """Query oil, gas, and hydrogen pipelines. + + Args: + pipeline_type: Filter by type (oil, gas, hydrogen). + status: Filter by status (active, destroyed, proposed, etc.) + + Returns: + Dict with pipelines[], count, by_type{}, by_status{}, source, timestamp. + """ + pipes = query_pipelines(pipeline_type=pipeline_type, status=status) + + by_type: dict[str, int] = {} + by_status: dict[str, int] = {} + for p in pipes: + by_type[p["type"]] = by_type.get(p["type"], 0) + 1 + by_status[p["status"]] = by_status.get(p["status"], 0) + 1 + + return { + "pipelines": pipes, + "count": len(pipes), + "total_in_database": len(PIPELINES), + "by_type": by_type, + "by_status": by_status, + "filters": {"pipeline_type": pipeline_type, "status": status}, + "source": "static-geospatial", + "timestamp": _utc_now_iso(), + } + + +async def fetch_nuclear_facilities( + facility_type: str | None = None, + country: str | None = None, + status: str | None = None, +) -> dict: + """Query nuclear power plants, enrichment sites, and research reactors. + + Args: + facility_type: Filter by type (power, enrichment, research, reprocessing, decommissioned). + country: Filter by country name or ISO-3 code. + status: Filter by status (operational, construction, shutdown, etc.) + + Returns: + Dict with facilities[], count, total_capacity_mw, by_type{}, by_status{}, source, timestamp. + """ + facilities = query_nuclear(facility_type=facility_type, country=country, status=status) + + total_cap = sum(f.get("capacity_mw", 0) for f in facilities) + by_type: dict[str, int] = {} + by_status: dict[str, int] = {} + for f in facilities: + by_type[f["type"]] = by_type.get(f["type"], 0) + 1 + by_status[f["status"]] = by_status.get(f["status"], 0) + 1 + + return { + "facilities": facilities, + "count": len(facilities), + "total_in_database": len(NUCLEAR_FACILITIES), + "total_capacity_mw": total_cap, + "by_type": by_type, + "by_status": by_status, + "filters": {"facility_type": facility_type, "country": country, "status": status}, + "source": "static-geospatial", + "timestamp": _utc_now_iso(), + }