Merge pull request #982 from LibreQoE/ui_fix_2

Node Manager UI refresh, Site/Flow map improvements, ASN Analysis, and integration fixes
This commit is contained in:
Robert Chacón authored and GitHub committed 2026-03-16 08:53:50 -06:00
commit 9507be94d4
94 files changed
+6986 -1198

No files matched your search

+1 -1
View File
@@ -131,7 +131,7 @@ To ensure the network.json is always overwritten with the newest version pulled
Edit the file to set the value of `always_overwrite_network_json` to `true`.
Then, run `sudo systemctl restart lqosd`.
You have the option to run integrationUISP.py automatically on boot and every X minutes (set by the parameter `queue_refresh_interval_mins`), which is highly recommended. This can be enabled by setting ```enable_uisp = true``` in `/etc/lqos.conf`. Once set, run `sudo systemctl restart lqos_scheduler`.
You have the option to run `uisp_integration` automatically on boot and every X minutes (set by the parameter `queue_refresh_interval_mins`), which is highly recommended. This can be enabled by setting ```enable_uisp = true``` in `/etc/lqos.conf`. Once set, run `sudo systemctl restart lqos_scheduler`.
### UISP Overrides
+3 -2
View File
@@ -21,9 +21,10 @@ flowchart LR
- Implementado en Rust.
- Ejecuta la UI local disponible en `http://a.b.c.d:9123`.
- Hospeda páginas de WebUI (Node Manager) como:
- Mapa de flujos
- Site Map
- Flow Globe
- Árbol de red
- Explorador ASN
- ASN Analysis
- Árbol/Pesos de CPU
- Editores de configuración para integraciones (UISP, Splynx, Netzur, VISP, etc.)
+20
View File
@@ -87,6 +87,26 @@ flowchart LR
Network.json permite a los operadores de internet (ISP) definir una topología de red jerárquica o plana.
Cada nodo de topología puede incluir opcionalmente un campo `"id"`. Este campo está pensado para transportar un identificador estable del CRM/NMS de origen cuando exista. LibreQoS todavía sigue haciendo match de jerarquía y overrides por nombre de nodo, no por este ID, así que por ahora `"id"` es metadata.
Formato recomendado:
```json
{
"Tower_A": {
"id": "uisp:site:abc123",
"downloadBandwidthMbps": 1000,
"uploadBandwidthMbps": 1000,
"type": "site"
}
}
```
Notas:
- Use IDs string con namespace como `uisp:site:<id>`, `splynx:network_site:<id>` o `sonar:ap:<id>`.
- Los nodos generados solo por LibreQoS pueden usar IDs generados estables como `libreqos:generated:uisp:site:orphans`.
- También pueden aparecer campos de metadata específicos de integración como `uisp_site` y `uisp_device` junto con el campo genérico `id`.
Si planea usar las integraciones ya incluidas en LibreQoS de UISP, Splynx o Netzur, no necesita todavía crear un archivo network.json.
Si planea usar la integración ya incluida de UISP, el archivo network.json se creará en automático durante la primera ejecución (siempre y cuando network.json no exista previamente).
+15 -1
View File
@@ -21,7 +21,7 @@ Si todavía no eligió ruta de despliegue, empiece por [Quickstart](quickstart-e
- Comportamiento común de integración: `Configuration -> Integrations`
- Campos por integración: `Configuration -> Integrations`
- Validación de salud tras cambios: `WebUI -> Scheduler Status` y `WebUI -> Urgent Issues`
- Validación de resultados/topología: `WebUI -> Network Tree Overview` y `Flow Map`
- Validación de resultados/topología: `WebUI -> Network Tree Overview` y `Flow Globe`
## Integraciones incluidas
@@ -40,6 +40,20 @@ Cuando hay integraciones habilitadas:
- `network.json` también puede sobrescribirse según configuración (por ejemplo `always_overwrite_network_json`).
- Las ediciones manuales pueden sobrescribirse en el siguiente ciclo de refresco.
## Soporte para IDs de nodos de topología
LibreQoS soporta un campo genérico opcional `"id"` en los nodos de `network.json`. Este campo está pensado para transportar identificadores estables del sistema de integración cuando sea posible. En la versión actual, el campo es informativo y todavía no es la clave autoritativa para shaping u overrides.
| Integración | Soporte de ID de nodo en `network.json` | Notas |
|---|---|---|
| UISP | Sí | Sitios/dispositivos reales de UISP exportan `id` genérico más la metadata existente `uisp_site` / `uisp_device`. Los nodos sintéticos de LibreQoS usan IDs generados estables. |
| Splynx | Sí | Los nodos de topología de network sites y AP/site exportan `id` genérico. |
| Sonar | Sí | Los nodos de topología de sitios y AP exportan `id` genérico. |
| Netzur | Parcial | Se exporta solo cuando el payload upstream de zonas incluye un ID de zona estable. |
| VISP | No | El importador actual shapea servicios/dispositivos pero no construye nodos de topología en `network.json`. |
| Powercode | No | El importador actual no construye nodos de topología en `network.json`. |
| WISPGate | No | El importador actual no construye nodos de topología a partir de identificadores estables upstream. |
## Manejo común de velocidades de cliente
Para las integraciones incluidas que importan velocidades brutas de plan de suscriptor, LibreQoS aplica la misma regla compartida antes de escribir `ShapedDevices.csv`:
+21 -6
View File
@@ -12,12 +12,26 @@ Esta página documenta las vistas clave de la WebUI (Node Manager) y su comporta
- Vista jerárquica de nodos/circuitos desde la perspectiva del shaper.
- Útil para identificar cuellos de botella y patrones de utilización padre/hijo.
### Mapa de flujos
- Visualización geográfica de flujos según geolocalización de endpoints.
### Site Map
- Mapa operativo plano de sitios y APs usando geodatos importados de nodos.
- Usa QoO por defecto con un selector alternativo para RTT, mientras el tamaño del marcador refleja el throughput combinado reciente.
- Usa un promedio del lado cliente de 30 segundos a partir de `NetworkTree`, sin agregar trabajo de rollup en el backend.
- Los APs pueden heredar coordenadas del sitio padre solo para visualización cuando faltan coordenadas explícitas.
- Usa un mapa base local con estilo LibreQoS con bordes de país/estado, costas, lagos principales, ríos principales, áreas marinas, superposiciones sutiles de regiones físicas y contexto de autopistas principales a mayor zoom para orientación geográfica.
- Site Map utiliza una capa local de carreteras derivada de Natural Earth para ayudar con la orientación, manteniendo el resto del mapa base discreto y operativo.
### Flow Globe
- Visualización geográfica de flujos basada en la geolocalización de endpoints.
- Usa un globo temático con bordes de países para contexto geográfico.
- Los marcadores de endpoints usan latencia por defecto, con un selector para cambiar entre latencia y throughput.
- El tamaño del marcador indica el volumen reciente de tráfico.
- Pase el cursor para detalles rápidos o haga clic en un marcador/cluster para fijar sus detalles en el panel lateral.
- Requiere volumen de datos reciente suficiente.
### Explorador ASN
- Exploración por ASN (volumen, RTT/retransmisiones y detalle de flujos asociados).
### ASN Analysis
- Página operativa ASN en vivo que combina un ranking top-20 de ASN, gráfico de burbujas latencia-vs-tráfico, franja mínima de KPIs del ASN seleccionado, gráfico de tendencia ASN de 15 minutos y la sección integrada de Flow Evidence.
- Soporta modos de ranking `Impact` y `Throughput`, manteniendo la evidencia de flujos ASN en la misma página.
- La ruta heredada `ASN Explorer` ahora redirige aquí para conservar compatibilidad con marcadores antiguos.
- Resultados vacíos suelen indicar poco dato reciente, no necesariamente falla.
### Árbol/ponderación de CPU
@@ -51,9 +65,10 @@ Esta página documenta las vistas clave de la WebUI (Node Manager) y su comporta
## Comportamiento de vistas vacías
Las siguientes vistas pueden verse vacías cuando hay poco dato:
- Mapa de flujos
- Site Map
- Flow Globe
- Sankey del árbol de red
- Explorador ASN
- ASN Analysis / Flow Evidence
Si ocurre:
1. Confirme que `lqosd` está saludable.
@@ -64,7 +64,7 @@ flowchart LR
2. El comportamiento de `network.json` coincide con la politica de overwrite.
3. La salud de vistas en WebUI es correcta.
4. Revise `Scheduler Status` y `Urgent Issues`.
5. Revise `Network Tree Overview` y `Flow Map`.
5. Revise `Network Tree Overview` y `Flow Globe`.
6. La colocacion de padres y distribucion de colas se ve estable.
## Fallas Comunes
+3 -3
View File
@@ -11,7 +11,7 @@ Use esta tabla para ir al primer check rápidamente.
| No se puede acceder a la WebUI | `systemctl status lqosd` | N/A (UI no disponible) | No hay WebUI en x.x.x.x:9123 |
| Hay tráfico pero no hace shaping | verificar `to_internet` / `to_network` y servicios | WebUI Dashboard | LibreQoS está en ejecución, pero no hace shaping |
| Scheduler no saludable | revisar logs de `lqosd` y `lqos_scheduler` | WebUI -> Scheduler Status | El estado del scheduler en WebUI aparece no saludable |
| Vistas de topología/flujo vacías | confirmar tráfico reciente y estado de `lqosd` | WebUI -> Flow Map / Tree / ASN Explorer | Flow Map / Tree Overview / ASN Explorer aparecen en blanco |
| Vistas de topología/flujo vacías | confirmar tráfico reciente y estado de `lqosd` | WebUI -> Flow Globe / Tree / ASN Analysis | Flow Globe / Tree Overview / ASN Analysis aparecen en blanco |
| Aparece código urgente | abrir detalle y mapear código | WebUI -> Urgent Issues | Códigos de problemas urgentes y primeras acciones |
| Eventos de límite de circuitos | validar licencia y conteos mapped | Insight UI + WebUI -> Urgent Issues | Se alcanzó el límite de circuitos mapeados |
@@ -22,7 +22,7 @@ Use esta tabla para ir al primer check rápidamente.
- Estado de servicios y salud general: `WebUI -> Dashboard`
- Estado/readiness del scheduler: `WebUI -> Scheduler Status`
- Alertas prioritarias: `WebUI -> Urgent Issues`
- Visualización de topología/tráfico: `WebUI -> Network Tree Overview` y `Flow Map`
- Visualización de topología/tráfico: `WebUI -> Network Tree Overview` y `Flow Globe`
- Revisión de datos de shaping: `WebUI -> Shaped Devices Editor`
### Antes de pedir ayuda en chat: recolecte esta evidencia
@@ -155,7 +155,7 @@ Corrija errores en `ShapedDevices.csv` y/o `network.json`, luego:
sudo systemctl start lqos_scheduler
```
### Flow Map / Tree Overview / ASN Explorer aparecen en blanco
### Flow Globe / Tree Overview / ASN Analysis aparecen en blanco
Algunas vistas requieren suficiente dato reciente para renderizar.
+3 -2
View File
@@ -21,9 +21,10 @@ flowchart LR
- Coded in Rust.
- Runs the GUI available at http://a.b.c.d:9123
- Hosts WebUI pages such as:
- Flow Map
- Site Map
- Flow Globe
- Network Tree Overview
- ASN Explorer
- ASN Analysis
- CPU Tree / CPU Weights
- Configuration editors for integrations (UISP, Splynx, Netzur, VISP, etc.)
+20
View File
@@ -131,6 +131,26 @@ How overrides apply:
Network.json allows ISP operators to define a Hierarchical Network Topology, or Flat Network Topology.
Each topology node may optionally include an `"id"` field. This is intended to carry a stable node identifier from the source CRM/NMS when one exists. LibreQoS currently still matches hierarchy and overrides by node name, not by this ID, so `"id"` is metadata for now.
Recommended format:
```json
{
"Tower_A": {
"id": "uisp:site:abc123",
"downloadBandwidthMbps": 1000,
"uploadBandwidthMbps": 1000,
"type": "site"
}
}
```
Notes:
- Use namespaced string IDs such as `uisp:site:<id>`, `splynx:network_site:<id>`, or `sonar:ap:<id>`.
- Generated LibreQoS-only nodes may use stable generated IDs such as `libreqos:generated:uisp:site:orphans`.
- Existing integration-specific metadata fields such as `uisp_site` and `uisp_device` may also appear alongside the generic `id` field.
If you plan to use the built-in UISP, Splynx, or Netzur integrations, you do not need to create a network.json file quite yet.
If you plan to use the built-in UISP integration, it will create this automatically on its first run (assuming network.json is not already present).
+15 -1
View File
@@ -32,7 +32,7 @@ flowchart LR
- Integration defaults/common behavior: `Configuration -> Integrations`
- Per-integration configuration fields: `Configuration -> Integrations`
- Operational health checks after sync changes: `WebUI -> Scheduler Status` and `WebUI -> Urgent Issues`
- Topology/result validation: `WebUI -> Network Tree Overview` and `Flow Map`
- Topology/result validation: `WebUI -> Network Tree Overview` and `Flow Globe`
## Built-In Integrations
@@ -51,6 +51,20 @@ When integrations are enabled:
- `network.json` may also be overwritten depending on integration settings (for example `always_overwrite_network_json`).
- Manual edits may be overwritten on the next refresh cycle.
## Topology Node ID Support
LibreQoS supports an optional generic `"id"` field on `network.json` nodes. This field is intended to carry stable node identifiers from the integration source where possible. In the current release, the field is informational and is not yet the authoritative match key for shaping or overrides.
| Integration | `network.json` node ID support | Notes |
|---|---|---|
| UISP | Yes | Real UISP sites/devices export generic `id` plus existing `uisp_site` / `uisp_device` metadata. Synthetic LibreQoS nodes use stable generated IDs. |
| Splynx | Yes | Network sites and AP/site topology nodes export generic `id`. |
| Sonar | Yes | Site and AP topology nodes export generic `id`. |
| Netzur | Partial | Exported only when the upstream zone payload includes a stable zone ID. |
| VISP | No | Current importer shapes services/devices but does not build topology nodes in `network.json`. |
| Powercode | No | Current importer does not build topology nodes in `network.json`. |
| WISPGate | No | Current importer does not build topology nodes from stable upstream topology identifiers. |
## Common Client Rate Handling
For built-in integrations that import raw subscriber plan speeds, LibreQoS applies the same shared client-rate rule before writing `ShapedDevices.csv`:
+21 -6
View File
@@ -12,13 +12,27 @@ This page documents key WebUI (Node Manager) views and operational behavior in t
- Hierarchical topology view of nodes/circuits from the shaper perspective.
- Useful for spotting bottlenecks and parent/child utilization patterns.
### Flow Map
### Site Map
- Flat operational map of Sites and APs using imported node geodata.
- Defaults to QoO coloring with an RTT toggle, while marker size reflects recent combined throughput.
- Uses a 30-second client-side average from `NetworkTree` data rather than adding backend rollup work.
- APs can inherit parent site coordinates for display when explicit AP coordinates are missing.
- Uses a local LibreQoS-themed basemap with country/state borders, coastlines, major lakes, major rivers, marine areas, subtle physical-region overlays, and higher-zoom major-highway context for geographic orientation.
- Site Map uses a local Natural Earth-derived roads layer for orientation while keeping the rest of the basemap quiet and operational.
### Flow Globe
- Geographic flow visualization based on endpoint geolocation.
- Uses a theme-aware globe with country borders for geographic context.
- Endpoint markers default to latency mode, with a toggle to switch between latency and throughput coloring.
- Marker size indicates recent traffic volume.
- Hover a marker for quick details, or click a marker/cluster to pin its details in the side panel.
- Best used when enough recent flow data is available.
### ASN Explorer
- ASN-centric traffic exploration (volume, RTT/retransmit context, and related flow details).
- Empty results often indicate low recent data volume rather than failure.
### ASN Analysis
- Live ASN operations page combining a top-20 ASN leaderboard, latency-vs-traffic bubble chart, selected ASN KPI strip, 15-minute ASN trend chart, and embedded Flow Evidence.
- Supports `Impact` and `Throughput` ranking modes while keeping ASN flow evidence on the same page.
- The legacy `ASN Explorer` route now redirects here so older bookmarks still work.
- Empty results usually indicate low recent data volume rather than failure.
### CPU Tree / CPU Weights
- Shows queue/circuit distribution by CPU core.
@@ -51,9 +65,10 @@ This page documents key WebUI (Node Manager) views and operational behavior in t
## Common Empty-State Behavior
The following pages may appear sparse/blank when data is low:
- Flow Map
- Site Map
- Flow Globe
- Tree Overview Sankey views
- ASN Explorer
- ASN Analysis / Flow Evidence
When this happens:
1. Confirm `lqosd` is healthy.
+1 -1
View File
@@ -69,7 +69,7 @@ What this shows:
2. `network.json` behavior matches overwrite policy.
3. WebUI views are healthy.
4. Check `Scheduler Status` and `Urgent Issues`.
5. Check `Network Tree Overview` and `Flow Map`.
5. Check `Network Tree Overview` and `Flow Globe`.
6. Parent placement and queue distribution look sane (no unexpected hierarchy collapse).
## Common Failure Modes
+1 -1
View File
@@ -75,7 +75,7 @@ See [Troubleshooting](troubleshooting.md#urgent-issue-codes-and-first-actions) f
3. Run/observe `lqos_scheduler` and `lqosd` logs after each change.
4. Validate WebUI:
- CPU Tree / CPU Weights distribution
- Flow Map/ASN/Tree behavior
- Flow Globe/ASN/Tree behavior
- scheduler status and urgent issues
5. Keep a rollback copy of prior integration settings and topology files.
+3 -3
View File
@@ -11,7 +11,7 @@ Need definitions for licensing/scheduler terms? See the [Glossary](glossary.md).
| Cannot access WebUI | `systemctl status lqosd` | N/A (UI unavailable) | No WebUI at x.x.x.x:9123 |
| Traffic is not shaping | verify `to_internet` / `to_network`, service state | WebUI Dashboard | LibreQoS Is Running, But Traffic Not Shaping |
| Scheduler appears unhealthy | check `lqosd` and `lqos_scheduler` logs | WebUI -> Scheduler Status | Scheduler status in WebUI looks unhealthy |
| Topology/flow views blank | confirm recent traffic and `lqosd` health | WebUI -> Flow Map / Tree / ASN Explorer | Flow Map / Tree Overview / ASN Explorer appears blank |
| Topology/flow views blank | confirm recent traffic and `lqosd` health | WebUI -> Flow Globe / Tree / ASN Analysis | Flow Globe / Tree Overview / ASN Analysis appears blank |
| Urgent issue code appears | open issue details and map code | WebUI -> Urgent Issues | Urgent issue codes and first actions |
| Mapped circuit cap events | validate license state and mapped counts | Insight UI + WebUI -> Urgent Issues | Mapped circuit limit reached |
@@ -22,7 +22,7 @@ Need definitions for licensing/scheduler terms? See the [Glossary](glossary.md).
- Service/health overview: `WebUI -> Dashboard`
- Scheduler readiness: `WebUI -> Scheduler Status`
- High-priority alerts: `WebUI -> Urgent Issues`
- Topology/traffic visualization: `WebUI -> Network Tree Overview` and `Flow Map`
- Topology/traffic visualization: `WebUI -> Network Tree Overview` and `Flow Globe`
- Shaped records review: `WebUI -> Shaped Devices Editor`
### Before asking in chat: collect this evidence
@@ -181,7 +181,7 @@ Once you have identified the error and fixed ShapedDevices.csv and/or Network.js
```sudo systemctl start lqos_scheduler```
### Flow Map / Tree Overview / ASN Explorer appears blank
### Flow Globe / Tree Overview / ASN Analysis appears blank
Some views require enough recent data to render meaningfully. If pages look empty:
1. Confirm `lqosd` is healthy.
+1 -2
View File
@@ -25,7 +25,6 @@ LQOS_FILES=(
integrationRestHttp.py
integrationSonar.py
integrationSplynx.py
integrationUISP.py
integrationVISP.py
integrationWISPGate.py
LibreQoS.py
@@ -176,7 +175,7 @@ for file in "${LQOS_FILES[@]}"; do
cp "$file" "$LQOS_DIR" || echo "Error copying $file"
done
# Ensure update_api.sh is executable in the package
# Ensure helper scripts are executable in the package
if [ -f "$LQOS_DIR/update_api.sh" ]; then
chmod a+x "$LQOS_DIR/update_api.sh" || true
fi
+24 -2
View File
@@ -97,6 +97,16 @@ def nodeTypeToString(integer):
string = 'device'
return(string)
def syntheticNetworkJsonId(scope: str, nodeType: str, name: str) -> str:
parts = []
for ch in str(name).lower():
if ch.isalnum():
parts.append(ch)
elif not parts or parts[-1] != '-':
parts.append('-')
slug = ''.join(parts).strip('-')
return f"libreqos:generated:{scope}:{nodeType}:{slug}"
class NetworkNode:
# Defines a node on a LibreQoS network graph.
# Nodes default to being disconnected, and
@@ -114,8 +124,11 @@ class NetworkNode:
ipv6: List
address: str
mac: str
networkJsonId: str
latitude: float
longitude: float
def __init__(self, id: str, displayName: str = "", parentId: str = "", type: NodeType = NodeType.site, download: int = generated_pn_download_mbps(), upload: int = generated_pn_upload_mbps(), ipv4: List = [], ipv6: List = [], address: str = "", mac: str = "", customerName: str = "") -> None:
def __init__(self, id: str, displayName: str = "", parentId: str = "", type: NodeType = NodeType.site, download: int = generated_pn_download_mbps(), upload: int = generated_pn_upload_mbps(), ipv4: List = [], ipv6: List = [], address: str = "", mac: str = "", customerName: str = "", networkJsonId: str = "", latitude = None, longitude = None) -> None:
self.id = id
self.parentIndex = 0
self.type = type
@@ -131,6 +144,9 @@ class NetworkNode:
self.address = address
self.customerName = customerName
self.mac = mac
self.networkJsonId = networkJsonId
self.latitude = latitude
self.longitude = longitude
class NetworkGraph:
@@ -319,7 +335,8 @@ class NetworkGraph:
siteNode = NetworkNode(
id=str(node.id) + "_gen",
displayName="(Generated Site) " + node.displayName,
type=NodeType.site
type=NodeType.site,
networkJsonId=syntheticNetworkJsonId("graph", "site", node.displayName),
)
siteNode.parentIndex = node.parentIndex
node.parentId = siteNode.id
@@ -453,6 +470,11 @@ class NetworkGraph:
"uploadBandwidthMbps": self.nodes[idx].uploadMbps,
'type': nodeTypeToString(self.nodes[idx].type),
}
if self.nodes[idx].networkJsonId:
node["id"] = self.nodes[idx].networkJsonId
if self.nodes[idx].latitude is not None and self.nodes[idx].longitude is not None:
node["latitude"] = self.nodes[idx].latitude
node["longitude"] = self.nodes[idx].longitude
children = {}
hasChildren = False
+5
View File
@@ -78,6 +78,10 @@ def createShaper() -> NetworkGraph:
continue
if name not in parents:
parents[name] = parent_counter
zone_id = zone.get("id", zone.get("zone_id"))
network_json_id = ""
if zone_id not in (None, ""):
network_json_id = f"netzur:zone:{zone_id}"
net.addRawNode(
NetworkNode(
id=parent_counter,
@@ -85,6 +89,7 @@ def createShaper() -> NetworkGraph:
type=NodeType.site,
download=_apply_rate(zone.get("capacity_download_mbps")),
upload=_apply_rate(zone.get("capacity_upload_mbps")),
networkJsonId=network_json_id,
)
)
parent_counter += 1
+7 -5
View File
@@ -123,13 +123,15 @@ def getSitesAndAps():
sites = []
aps = []
for site in sites_and_aps:
site_has_ap = False
for item in site['inventory_items']['entities']:
ips = findIPs(item)
if ips:
aps.append({'parent': f"site_{site['id']}",'id': f"ap_{item['id']}", 'model': item['inventory_model_id'], 'ip': ips[0]}) # Using the first IP in the list here because each IP should only have 1 IP assigned.
aps.append({'parent': f"site_{site['id']}",'id': f"ap_{item['id']}", 'raw_id': item['id'], 'model': item['inventory_model_id'], 'ip': ips[0]}) # Using the first IP in the list here because each IP should only have 1 IP assigned.
site_has_ap = True
if aps: #We don't care about sites that have equipment but no IP addresses.
sites.append({'id': f"site_{site['id']}", 'name': site['name']})
if site_has_ap: #We don't care about sites that have equipment but no IP addresses.
sites.append({'id': f"site_{site['id']}", 'raw_id': site['id'], 'name': site['name']})
return sites, aps
def getAccounts(sonar_active_status_ids):
@@ -298,11 +300,11 @@ def createShaper():
break
for site in sites:
net.addRawNode(NetworkNode(id=site['id'], displayName=site['name'], parentId="", type=NodeType.site))
net.addRawNode(NetworkNode(id=site['id'], displayName=site['name'], parentId="", type=NodeType.site, networkJsonId=f"sonar:site:{site['raw_id']}"))
for ap in aps:
if ap['cpe_macs']: # I don't think we care about Aps with no customers.
net.addRawNode(NetworkNode(id=ap['id'], displayName=ap['name'], parentId=ap['parent'], type=NodeType.ap))
net.addRawNode(NetworkNode(id=ap['id'], displayName=ap['name'], parentId=ap['parent'], type=NodeType.ap, networkJsonId=f"sonar:ap:{ap['raw_id']}"))
for account in accounts:
if account['parent']:
+43 -5
View File
@@ -18,6 +18,28 @@ from integrationCommon import NetworkGraph, NetworkNode, NodeType
import os
import csv
def parse_gps_pair(value):
"""
Parse a Splynx `gps` string in `lat,lon` form.
Returns `(latitude, longitude)` or `(None, None)` for malformed data.
"""
if value is None:
return (None, None)
text = str(value).strip()
if not text:
return (None, None)
parts = [part.strip() for part in text.split(',')]
if len(parts) != 2:
return (None, None)
try:
latitude = float(parts[0])
longitude = float(parts[1])
except (TypeError, ValueError):
return (None, None)
if latitude < -90 or latitude > 90 or longitude < -180 or longitude > 180:
return (None, None)
return (latitude, longitude)
def build_online_ip_maps(customersOnline):
"""
Build maps of service_id -> [ipv4], [ipv6] from customers-online payload.
@@ -257,6 +279,7 @@ def run_splynx_pipeline(strategy_name: str):
site_id_to_node_id = {}
site_id_to_name = {}
site_id_to_address = {}
site_id_to_coords = {}
if network_sites:
for site in network_sites:
site_id = site.get('id')
@@ -268,6 +291,7 @@ def run_splynx_pipeline(strategy_name: str):
site_id_to_node_id[site_id] = node_id
site_id_to_name[site_id] = name
site_id_to_address[site_id] = address
site_id_to_coords[site_id] = parse_gps_pair(site.get('gps'))
def ap_node_id(raw_id):
return f"ap_{raw_id}"
@@ -298,7 +322,10 @@ def run_splynx_pipeline(strategy_name: str):
download = siteBandwidth[nodeName]["download"]
upload = siteBandwidth[nodeName]["upload"]
node = NetworkNode(id=node_id, displayName=nodeName, type=NodeType.site,
parentId=None, download=download, upload=upload, address=address)
parentId=None, download=download, upload=upload, address=address,
networkJsonId=f"splynx:network_site:{site_id}",
latitude=site_id_to_coords.get(site_id, (None, None))[0],
longitude=site_id_to_coords.get(site_id, (None, None))[1])
net.addRawNode(node)
created_ap = 0
for ap_id, ap_device in ap_nodes.items():
@@ -312,8 +339,11 @@ def run_splynx_pipeline(strategy_name: str):
site_id = ap_device.get('network_site_id')
if site_id in site_id_to_node_id:
parent_id = site_id_to_node_id[site_id]
latitude, longitude = parse_gps_pair(ap_device.get('gps'))
node = NetworkNode(id=ap_node_id(ap_id), displayName=nodeName, type=NodeType.ap,
parentId=parent_id, download=download, upload=upload, address=None)
parentId=parent_id, download=download, upload=upload, address=None,
networkJsonId=f"splynx:ap:{ap_id}",
latitude=latitude, longitude=longitude)
net.addRawNode(node)
created_ap += 1
print(f"Created {created_ap} AP nodes (Network Sites mode)")
@@ -327,7 +357,8 @@ def run_splynx_pipeline(strategy_name: str):
if nodeName in siteBandwidth:
download = siteBandwidth[nodeName]["download"]
upload = siteBandwidth[nodeName]["upload"]
node = NetworkNode(id=ap_id, displayName=nodeName, type=NodeType.ap, parentId=None, download=download, upload=upload, address=None)
latitude, longitude = parse_gps_pair(ap_device.get('gps'))
node = NetworkNode(id=ap_id, displayName=nodeName, type=NodeType.ap, parentId=None, download=download, upload=upload, address=None, networkJsonId=f"splynx:ap:{ap_id}", latitude=latitude, longitude=longitude)
net.addRawNode(node)
return
# ap_site and full
@@ -649,6 +680,10 @@ def createInfrastructureNodes(net, monitoring, hardware_name, hardware_parent, h
"""
Create site and AP nodes from monitoring data.
"""
monitoring_gps = {}
for dev in monitoring:
if 'id' in dev:
monitoring_gps[str(dev.get('id'))] = parse_gps_pair(dev.get('gps'))
for device_num in hardware_name:
parent_id = None
if device_num in hardware_parent.keys():
@@ -661,12 +696,15 @@ def createInfrastructureNodes(net, monitoring, hardware_name, hardware_parent, h
download = siteBandwidth[nodeName]["download"]
upload = siteBandwidth[nodeName]["upload"]
nodeType = hardware_type[device_num]
latitude, longitude = monitoring_gps.get(str(device_num), (None, None))
if nodeType == 'AP':
node = NetworkNode(id=device_num, displayName=nodeName, type=NodeType.ap,
parentId=parent_id, download=download, upload=upload, address=None)
parentId=parent_id, download=download, upload=upload, address=None,
networkJsonId=f"splynx:ap:{device_num}", latitude=latitude, longitude=longitude)
else:
node = NetworkNode(id=device_num, displayName=nodeName, type=NodeType.site,
parentId=parent_id, download=download, upload=upload, address=None)
parentId=parent_id, download=download, upload=upload, address=None,
networkJsonId=f"splynx:site:{device_num}", latitude=latitude, longitude=longitude)
net.addRawNode(node)
def findBestParentNode(serviceItem, hardware_name, ipForRouter, sectorForRouter):
-567
View File
@@ -1,567 +0,0 @@
from pythonCheck import checkPythonVersion
checkPythonVersion()
import requests
import os
import csv
from datetime import datetime, timedelta
from integrationCommon import apply_client_bandwidth_multiplier, isIpv4Permitted, fixSubnet
from liblqos_python import uisp_site, uisp_strategy, overwrite_network_json_always, uisp_suspended_strategy, \
airmax_capacity, ltu_capacity, use_ptmp_as_parent, uisp_base_url, uisp_auth_token, \
generated_pn_download_mbps, generated_pn_upload_mbps
def uispRequest(target):
# Sends an HTTP request to UISP and returns the
# result in JSON. You only need to specify the
# tail end of the URL, e.g. "sites"
url = uisp_base_url() + "/nms/api/v2.1/" + target
headers = {'accept': 'application/json', 'x-auth-token': uisp_auth_token()}
r = requests.get(url, headers=headers, timeout=60)
return r.json()
def buildFlatGraph():
# Builds a high-performance (but lacking in site or AP bandwidth control)
# network.
from integrationCommon import NetworkGraph, NetworkNode, NodeType
# Load network sites
print("Loading Data from UISP")
sites = uispRequest("sites")
devices = uispRequest("devices?withInterfaces=true&authorized=true")
# Build a basic network adding every client to the tree
print("Building Flat Topology")
net = NetworkGraph()
for site in sites:
type = site['identification']['type']
if type == "endpoint":
id = site['identification']['id']
address = site['description']['address']
customerName = ''
name = site['identification']['name']
type = site['identification']['type']
download = generated_pn_download_mbps()
upload = generated_pn_upload_mbps()
if (site['qos']['downloadSpeed']) and (site['qos']['uploadSpeed']):
download = apply_client_bandwidth_multiplier(site['qos']['downloadSpeed']/1000000)
upload = apply_client_bandwidth_multiplier(site['qos']['uploadSpeed']/1000000)
if site['identification'] is not None and site['identification']['suspended'] is not None and site['identification']['suspended'] == True:
if uisp_suspended_strategy() == "ignore":
print("WARNING: Site " + name + " is suspended")
continue
if uisp_suspended_strategy() == "slow":
print("WARNING: Site " + name + " is suspended")
download = 1
upload = 1
if site['identification']['status'] == "disconnected":
print("WARNING: Site " + name + " is disconnected")
node = NetworkNode(id=id, displayName=name, type=NodeType.client, download=download, upload=upload, address=address, customerName=customerName)
net.addRawNode(node)
for device in devices:
if device['identification']['site'] is not None and device['identification']['site']['id'] == id:
# The device is at this site, so add it
ipv4 = []
ipv6 = []
for interface in device["interfaces"]:
for ip in interface["addresses"]:
ip = ip["cidr"]
if isIpv4Permitted(ip):
ip = fixSubnet(ip)
if ip not in ipv4:
ipv4.append(ip)
# TODO: Figure out Mikrotik IPv6?
mac = device['identification']['mac']
net.addRawNode(NetworkNode(id=device['identification']['id'], displayName=device['identification']
['name'], parentId=id, type=NodeType.device, ipv4=ipv4, ipv6=ipv6, mac=mac))
# Finish up
net.prepareTree()
net.plotNetworkGraph(False)
if net.doesNetworkJsonExist():
if overwrite_network_json_always():
net.createNetworkJson()
else:
print("network.json already exists and overwriteNetworkJSONalways set to False. Leaving in-place.")
else:
net.createNetworkJson()
net.createShapedDevices()
def linkSiteTarget(link, direction):
# Helper function to extract the site ID from a data link. Returns
# None if not present.
if link[direction]['site'] is not None:
return link[direction]['site']['identification']['id']
return None
def findSiteLinks(dataLinks, siteId):
# Searches the Data Links for any links to/from the specified site.
# Returns a list of site IDs that are linked to the specified site.
links = []
for dl in dataLinks:
fromSiteId = linkSiteTarget(dl, "from")
if fromSiteId is not None and fromSiteId == siteId:
# We have a link originating in this site.
target = linkSiteTarget(dl, "to")
if target is not None:
links.append(target)
toSiteId = linkSiteTarget(dl, "to")
if toSiteId is not None and toSiteId == siteId:
# We have a link originating in this site.
target = linkSiteTarget(dl, "from")
if target is not None:
links.append(target)
return links
def buildSiteBandwidths():
# Builds a dictionary of site bandwidths from the integrationUISPbandwidths.csv file.
siteBandwidth = {}
if os.path.isfile("integrationUISPbandwidths.csv"):
with open('integrationUISPbandwidths.csv') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
next(csv_reader)
for row in csv_reader:
name, download, upload = row
download = int(float(download))
upload = int(float(upload))
siteBandwidth[name] = {"download": download, "upload": upload}
return siteBandwidth
def findApCapacities(devices, siteBandwidth):
# Searches the UISP devices for APs and adds their capacities to the siteBandwidth dictionary.
for device in devices:
if device['identification']['role'] == "ap":
name = device['identification']['name']
if not name in siteBandwidth and device['overview']['downlinkCapacity'] and device['overview']['uplinkCapacity']:
safeToUse = True
download = int(device['overview']
['downlinkCapacity'] / 1000000)
upload = int(device['overview']['uplinkCapacity'] / 1000000)
dlRatio = None
if device['identification']['type'] == 'airMax':
download, upload = airMaxCapacityCorrection(device, download, upload)
elif device['identification']['model'] == 'LTU-Rocket':
download = download * ltu_capacity()
upload = upload * ltu_capacity()
if device['identification']['model'] == 'WaveAP':
if (download < 500) or (upload < 500):
download = 2450
upload = 2450
if (download < 15) or (upload < 15):
print("WARNING: Device '" + device['identification']['hostname'] + "' has unusually low capacity (" + str(download) + '/' + str(upload) + " Mbps). Discarding in favor of parent site rates.")
safeToUse = False
if safeToUse:
siteBandwidth[device['identification']['name']] = {
"download": download, "upload": upload}
def airMaxCapacityCorrection(device, download, upload):
dlRatio = None
for interface in device['interfaces']:
if ('wireless' in interface) and (interface['wireless'] != None):
if 'dlRatio' in interface['wireless']:
dlRatio = interface['wireless']['dlRatio']
# UISP reports unrealistically high capacities for airMax.
# For fixed frame, multiply capacity by the ratio for download/upload.
# For Flexible Frame, use 65% of reported capcity.
# 67/33
if dlRatio == 67:
download = download * 0.67
upload = upload * 0.33
# 50/50
elif dlRatio == 50:
download = download * 0.50
upload = upload * 0.50
# Flexible frame
elif dlRatio == None:
download = download * airmax_capacity()
upload = upload * airmax_capacity()
return (download, upload)
def findAirfibers(devices, generatedPNDownloadMbps, generatedPNUploadMbps):
foundAirFibersBySite = {}
for device in devices:
if device['identification']['site']['type'] == 'site':
if device['identification']['role'] == "station":
if device['identification']['type'] == "airFiber" or device['identification']['type'] == "airMax":
if device['overview']['status'] == 'active':
if device['overview']['downlinkCapacity'] is not None and device['overview']['uplinkCapacity'] is not None:
# Exclude PtMP clients. Those will be found by findNodesBranchedOffPtMP
safeToUse = True
if ("wirelessMode" in device["overview"]) and (device["overview"]["wirelessMode"] != None):
if device["overview"]["wirelessMode"] == "sta-ptmp":
safeToUse = False
# Exclude Links With Bad Data in UISP
if ("overview" in device) and (device["overview"] != None):
if "wirelessMode" in device["overview"]:
if device["overview"]["wirelessMode"] == None:
safeToUse = False
if (safeToUse):
#print(device['identification']['model'])
download = int(device['overview']['downlinkCapacity']/ 1000000)
upload = int(device['overview']['uplinkCapacity']/ 1000000)
# Correct AirMax Capacities
if device['identification']['type'] == 'airMax':
download, upload = airMaxCapacityCorrection(device, download, upload)
# Make sure to factor in gigabit port for AF60/AF60-LRs
if (device['identification']['model'] == "AF60-LR") or (device['identification']['model'] == "AF60"):
download = min(download,950)
upload = min(upload,950)
if device['identification']['site']['id'] in foundAirFibersBySite:
if (download > foundAirFibersBySite[device['identification']['site']['id']]['download']) or (upload > foundAirFibersBySite[device['identification']['site']['id']]['upload']):
foundAirFibersBySite[device['identification']['site']['id']]['download'] = download
foundAirFibersBySite[device['identification']['site']['id']]['upload'] = upload
#print(device['identification']['name'] + ' will override bandwidth for site ' + device['identification']['site']['name'])
else:
foundAirFibersBySite[device['identification']['site']['id']] = {'download': download, 'upload': upload}
return foundAirFibersBySite
def buildSiteList(sites, dataLinks):
# Builds a list of sites, including their IDs, names, and connections.
# Connections are determined by the dataLinks list.
siteList = []
for site in sites:
newSite = {
'id': site['identification']['id'],
'name': site['identification']['name'],
'connections': findSiteLinks(dataLinks, site['identification']['id']),
'cost': 10000,
'parent': "",
'type': type,
}
siteList.append(newSite)
return siteList
def findInSiteList(siteList, name):
# Searches the siteList for a site with the specified name.
for site in siteList:
if site['name'] == name:
return site
return None
def findInSiteListById(siteList, id):
# Searches the siteList for a site with the specified name.
for site in siteList:
if site['id'] == id:
return site
return None
def debugSpaces(n):
# Helper function to print n spaces.
spaces = ""
for i in range(int(n)):
spaces = spaces + " "
return spaces
def walkGraphOutwards(siteList, root, routeOverrides):
def walkGraph(node, parent, cost, backPath):
site = findInSiteListById(siteList, node)
routeName = parent['name'] + "->" + site['name']
if routeName in routeOverrides:
# We have an overridden cost for this route, so use it instead
#print("--> Using overridden cost for " + routeName + ". New cost: " + str(routeOverrides[routeName]) + ".")
cost = routeOverrides[routeName]
if cost < site['cost']:
# It's cheaper to get here this way, so update the cost and parent.
site['cost'] = cost
site['parent'] = parent['id']
#print(debugSpaces(cost/10) + parent['name'] + "->" + site['name'] + " -> New cost: " + str(cost))
for connection in site['connections']:
if not connection in backPath:
#target = findInSiteListById(siteList, connection)
#print(debugSpaces((cost+10)/10) + site['name'] + " -> " + target['name'] + " (" + str(target['cost']) + ")")
newBackPath = backPath.copy()
newBackPath.append(site['id'])
walkGraph(connection, site, cost+10, newBackPath)
for connection in root['connections']:
# Force the parent since we're at the top
site = findInSiteListById(siteList, connection)
site['parent'] = root['id']
walkGraph(connection, root, 10, [root['id']])
def loadRoutingOverrides():
# Loads integrationUISProutes.csv and returns a set of "from", "to", "cost"
overrides = {}
if os.path.isfile("integrationUISProutes.csv"):
with open("integrationUISProutes.csv", "r") as f:
reader = csv.reader(f)
for row in reader:
if row and not row[0].startswith("#") and len(row) == 3:
fromSite, toSite, cost = row
overrides[fromSite.strip() + "->" + toSite.strip()] = int(cost)
#print(overrides)
return overrides
def findNodesBranchedOffPtMP(siteList, dataLinks, sites, rootSite, foundAirFibersBySite):
nodeOffPtMP = {}
if use_ptmp_as_parent():
for site in siteList:
id = site['id']
name = site['name']
if id != rootSite['id']:
if id not in foundAirFibersBySite:
trueParent = findInSiteListById(siteList, id)['parent']
#parent = findInSiteListById(siteList, id)['parent']
isTrueSite = False
for siteItem in sites:
if siteItem['identification']['id'] == id:
if siteItem['identification']['type'] == 'site':
isTrueSite = True
if isTrueSite:
if site['parent'] is not None:
parent = site['parent']
# Check how many links connect site to parent
# If only one link conencts them, we can then consdier if it's a node off PtMP
howManyLinksToParent = 0
for link in dataLinks:
if (link['to']['site'] is not None) and (link['to']['site']['identification'] is not None):
if ('identification' in link['to']['site']) and (link['to']['site']['identification'] is not None) and link['from'] is not None and link['from']['site'] is not None and link['from']['site']['identification'] is not None:
if link['from']['site']['identification']['id'] == trueParent:
if link['to']['site']['identification']['id'] == id:
howManyLinksToParent += 1
if howManyLinksToParent == 1:
for link in dataLinks:
if (link['to']['site'] is not None) and (link['to']['site']['identification'] is not None):
if ('identification' in link['to']['site']) and (link['to']['site']['identification'] is not None) and link['from'] is not None and link['from']['site'] is not None and link['from']['site']['identification'] is not None:
# Respect parent defined by topology and overrides
if link['from']['site']['identification']['id'] == trueParent:
if link['to']['site']['identification']['id'] == id:
if (link['from']['device']['overview']['wirelessMode'] == 'ap-ptmp') or (link['from']['device']['overview']['wirelessMode'] == 'ap'):
if 'overview' in link['to']['device']:
if ('downlinkCapacity' in link['to']['device']['overview']) and ('uplinkCapacity' in link['to']['device']['overview']):
if (link['to']['device']['overview']['downlinkCapacity'] is not None) and (link['to']['device']['overview']['uplinkCapacity'] is not None):
apID = link['from']['device']['identification']['id']
# Capacity of the PtMP client radio feeding the PoP will be used as the site bandwidth limit
download = int(round(link['to']['device']['overview']['downlinkCapacity']/1000000))
upload = int(round(link['to']['device']['overview']['uplinkCapacity']/1000000))
nodeOffPtMP[id] = {'download': download,
'upload': upload,
parent: apID
}
if use_ptmp_as_parent():
site['parent'] = apID
print('Site ' + name + ' will use PtMP AP ' + link['from']['device']['identification']['name'] + ' as parent from site ' + link['from']['site']['identification']['name'])
return siteList, nodeOffPtMP
def handleMultipleInternetNodes(sites, dataLinks, uispSite):
internetConnectedSites = []
for link in dataLinks:
if link['canDelete'] == False:
if link['from']['device'] is not None and link['to']['device'] is not None and link['from']['device']['identification']['id'] == link['to']['device']['identification']['id']:
siteID = link['from']['site']['identification']['id']
# Found internet link
internetConnectedSites.append(siteID)
if len(internetConnectedSites) > 1:
internetSite = {'id': '001', 'identification': {'id': '001', 'status': 'active', 'name': 'Internet', 'parent': None, 'type': 'site'}}
sites.append(internetSite)
uispSite = 'Internet'
for link in dataLinks:
if link['canDelete'] == False:
if link['from']['device'] is not None and link['to']['device'] is not None and link['from']['device']['identification']['id'] == link['to']['device']['identification']['id']:
link['from']['site']['identification']['id'] = '001'
link['from']['site']['identification']['name'] = 'Internet'
# Found internet link
internetConnectedSites.append(siteID)
print("Multiple Internet links detected. Will create 'Internet' root node.")
return(sites, dataLinks, uispSite)
def buildFullGraph():
# Attempts to build a full network graph, incorporating as much of the UISP
# hierarchy as possible.
from integrationCommon import NetworkGraph, NetworkNode, NodeType
uispSite = uisp_site()
# Load network sites
print("Loading Data from UISP")
sites = uispRequest("sites")
devices = uispRequest("devices?withInterfaces=true&authorized=true")
dataLinks = uispRequest("data-links?siteLinksOnly=true")
# If no uispSite listed, and there are multiple Internet-connected sites, then create Internet root node:
if uispSite == "":
sites, dataLinks, uispSite = handleMultipleInternetNodes(sites, dataLinks, uispSite)
# Build Site Capacities
print("Compiling Site Bandwidths")
siteBandwidth = buildSiteBandwidths()
print("Finding AP Capacities")
findApCapacities(devices, siteBandwidth)
# Create a list of just network sites
print('Creating list of sites')
siteList = buildSiteList(sites, dataLinks)
rootSite = findInSiteList(siteList, uispSite)
print("Finding PtP Capacities")
foundAirFibersBySite = findAirfibers(devices, generated_pn_download_mbps(), generated_pn_upload_mbps())
print('Creating list of route overrides')
routeOverrides = loadRoutingOverrides()
if rootSite is None:
print("ERROR: Unable to find root site in UISP")
return
print('Walking graph outwards')
walkGraphOutwards(siteList, rootSite, routeOverrides)
print("Finding PtMP Capacities")
siteList, nodeOffPtMP = findNodesBranchedOffPtMP(siteList, dataLinks, sites, rootSite, foundAirFibersBySite)
# Debug code: dump the list of site parents
# for s in siteList:
# if s['parent'] == "":
# p = "None"
# else:
# p = findInSiteListById(siteList, s['parent'])['name']
# print(s['name'] + " (" + str(s['cost']) + ") <-- " + p)
print("Building Topology")
net = NetworkGraph()
# Add all sites and client sites
for site in sites:
id = site['identification']['id']
name = site['identification']['name']
type = site['identification']['type']
download = generated_pn_download_mbps()
upload = generated_pn_upload_mbps()
address = ""
customerName = ""
parent = findInSiteListById(siteList, id)['parent']
if parent == "":
if site['identification']['parent'] is None:
parent = ""
else:
parent = site['identification']['parent']['id']
match type:
case "site":
nodeType = NodeType.site
customerName = name
if name in siteBandwidth:
# Use the CSV bandwidth values
download = siteBandwidth[name]["download"]
upload = siteBandwidth[name]["upload"]
else:
# Use limits from foundAirFibersBySite
if id in foundAirFibersBySite:
download = foundAirFibersBySite[id]['download']
upload = foundAirFibersBySite[id]['upload']
# If no airFibers were found, and node originates off PtMP, treat as child node of that PtMP AP
else:
if id in nodeOffPtMP:
if (nodeOffPtMP[id]['download'] >= download) or (nodeOffPtMP[id]['upload'] >= upload):
download = nodeOffPtMP[id]['download']
upload = nodeOffPtMP[id]['upload']
#parent = nodeOffPtMP[id]['parent']
siteBandwidth[name] = {
"download": download, "upload": upload}
case default:
nodeType = NodeType.client
address = site['description']['address']
try:
customerName = site["ucrm"]["client"]["name"]
except:
customerName = ""
if (site['qos']['downloadSpeed']) and (site['qos']['uploadSpeed']):
download = apply_client_bandwidth_multiplier(site['qos']['downloadSpeed']/1000000)
upload = apply_client_bandwidth_multiplier(site['qos']['uploadSpeed']/1000000)
if site['identification'] is not None and site['identification']['suspended'] is not None and site['identification']['suspended'] == True:
if uisp_suspended_strategy() == "ignore":
print("WARNING: Site " + name + " is suspended")
continue
if uisp_suspended_strategy() == "slow":
print("WARNING: Site " + name + " is suspended")
download = 2
upload = 2
if site['identification']['status'] == "disconnected":
print("WARNING: Site " + name + " is disconnected")
node = NetworkNode(id=id, displayName=name, type=nodeType,
parentId=parent, download=download, upload=upload, address=address, customerName=customerName)
# If this is the uispSite node, it becomes the root. Otherwise, add it to the
# node soup.
if name == uispSite:
net.replaceRootNode(node)
else:
net.addRawNode(node)
for device in devices:
if device['identification']['site'] is not None and device['identification']['site']['id'] == id:
# The device is at this site, so add it
ipv4 = []
ipv6 = []
for interface in device["interfaces"]:
for ip in interface["addresses"]:
ip = ip["cidr"]
if isIpv4Permitted(ip):
ip = fixSubnet(ip)
if ip not in ipv4:
ipv4.append(ip)
# TODO: Figure out Mikrotik IPv6?
mac = device['identification']['mac']
net.addRawNode(NetworkNode(id=device['identification']['id'], displayName=device['identification']
['name'], parentId=id, type=NodeType.device, ipv4=ipv4, ipv6=ipv6, mac=mac))
# Now iterate access points, and look for connections to sites
for node in net.nodes:
if node.type == NodeType.device:
for dl in dataLinks:
if dl['from']['device'] is not None and dl['from']['device']['identification']['id'] == node.id:
if dl['to']['site'] is not None and dl['from']['site']['identification']['id'] != dl['to']['site']['identification']['id']:
target = net.findNodeIndexById(
dl['to']['site']['identification']['id'])
if target > -1:
# We found the site
if net.nodes[target].type == NodeType.client or net.nodes[target].type == NodeType.clientWithChildren:
net.nodes[target].parentId = node.id
node.type = NodeType.ap
if node.displayName in siteBandwidth:
# Use the bandwidth numbers from the CSV file
node.uploadMbps = siteBandwidth[node.displayName]["upload"]
node.downloadMbps = siteBandwidth[node.displayName]["download"]
else:
# Add some defaults in case they want to change them
siteBandwidth[node.displayName] = {
"download": generated_pn_download_mbps(), "upload": generated_pn_upload_mbps()}
net.prepareTree()
print('Plotting network graph')
net.plotNetworkGraph(False)
if net.doesNetworkJsonExist():
if overwrite_network_json_always():
net.createNetworkJson()
else:
print("network.json already exists and overwriteNetworkJSONalways set to False. Leaving in-place.")
else:
net.createNetworkJson()
net.createShapedDevices()
# Save integrationUISPbandwidths.csv
# (the newLine fixes generating extra blank lines)
# Saves as .template as to not overwrite
with open('integrationUISPbandwidths.template.csv', 'w', newline='') as csvfile:
wr = csv.writer(csvfile, quoting=csv.QUOTE_ALL)
wr.writerow(['ParentNode', 'Download Mbps', 'Upload Mbps'])
for device in siteBandwidth:
entry = (
device, siteBandwidth[device]["download"], siteBandwidth[device]["upload"])
wr.writerow(entry)
def importFromUISP():
startTime = datetime.now()
match uisp_strategy():
case "full": buildFullGraph()
case default: buildFlatGraph()
endTime = datetime.now()
runTimeSeconds = ((endTime - startTime).seconds) + (((endTime - startTime).microseconds) / 1000000)
print("UISP import completed in " + "{:g}".format(round(runTimeSeconds,1)) + " seconds")
if __name__ == '__main__':
importFromUISP()
+1 -1
View File
@@ -5,7 +5,7 @@ edition = "2024"
license = "GPL-2.0-only"
[profile.release]
strip = yes
strip = true
lto = "thin"
incremental = false
+106
View File
@@ -78,6 +78,7 @@ impl NetworkJson {
pub fn load() -> Result<Self, NetworkJsonError> {
let mut nodes = vec![NetworkJsonNode {
name: "Root".to_string(),
id: None,
virtual_node: false,
max_throughput: (0.0, 0.0),
current_throughput: DownUpOrder::zeroed(),
@@ -92,6 +93,8 @@ impl NetworkJson {
immediate_parent: None,
rtt_buffer: RttBuffer::default(),
node_type: None,
latitude: None,
longitude: None,
heatmap: None,
qoq_heatmap: None,
}];
@@ -327,6 +330,15 @@ fn json_to_mbps(val: Option<&Value>) -> f64 {
.unwrap_or(0.0)
}
fn json_to_coordinate(val: Option<&Value>, min: f32, max: f32) -> Option<f32> {
val.and_then(|v| {
v.as_f64()
.or_else(|| v.as_str().and_then(|s| s.parse::<f64>().ok()))
})
.map(|n| n as f32)
.filter(|n| n.is_finite() && *n >= min && *n <= max)
}
fn recurse_node(
nodes: &mut Vec<NetworkJsonNode>,
name: &str,
@@ -353,6 +365,10 @@ fn recurse_node(
.unwrap_or(false);
let node = NetworkJsonNode {
id: json
.get("id")
.and_then(|v| v.as_str())
.map(std::string::ToString::to_string),
parents: parents.to_vec(),
max_throughput: (
json_to_mbps(json.get("downloadBandwidthMbps")),
@@ -373,6 +389,8 @@ fn recurse_node(
node_type: json
.get("type")
.map(|v| v.as_str().unwrap_or_default().to_string()),
latitude: json_to_coordinate(json.get("latitude"), -90.0, 90.0),
longitude: json_to_coordinate(json.get("longitude"), -180.0, 180.0),
heatmap: None,
qoq_heatmap: None,
};
@@ -440,6 +458,7 @@ mod test {
fn parse_network_json_from_value(value: Value) -> NetworkJson {
let mut nodes = vec![NetworkJsonNode {
name: "Root".to_string(),
id: None,
virtual_node: false,
max_throughput: (0.0, 0.0),
current_throughput: DownUpOrder::zeroed(),
@@ -454,6 +473,8 @@ mod test {
immediate_parent: None,
rtt_buffer: RttBuffer::default(),
node_type: None,
latitude: None,
longitude: None,
heatmap: None,
qoq_heatmap: None,
}];
@@ -552,4 +573,89 @@ mod test {
let encoded = serde_json::to_value(&transport).expect("transport must serialize");
assert_eq!(encoded["max_throughput"], serde_json::json!([1.5, 0.5]));
}
#[test]
fn parses_optional_node_ids_and_preserves_them_in_transport() {
let raw = serde_json::json!({
"Site_1": {
"id": "uisp:site:123",
"downloadBandwidthMbps": 1000,
"uploadBandwidthMbps": 1000,
"children": {
"AP_1": {
"id": "uisp:device:456",
"downloadBandwidthMbps": 500,
"uploadBandwidthMbps": 500,
"children": {}
}
}
}
});
let parsed = parse_network_json_from_value(raw);
let site = parsed
.nodes
.iter()
.find(|n| n.name == "Site_1")
.expect("Site_1 must be present");
let ap = parsed
.nodes
.iter()
.find(|n| n.name == "AP_1")
.expect("AP_1 must be present");
assert_eq!(site.id.as_deref(), Some("uisp:site:123"));
assert_eq!(ap.id.as_deref(), Some("uisp:device:456"));
assert_eq!(
site.clone_to_transit().id.as_deref(),
Some("uisp:site:123")
);
assert_eq!(
ap.clone_to_transit().id.as_deref(),
Some("uisp:device:456")
);
}
#[test]
fn parses_coordinate_metadata_tolerantly() {
let raw = serde_json::json!({
"Tower A": {
"latitude": "45.123",
"longitude": -111.75,
"downloadBandwidthMbps": 1000,
"uploadBandwidthMbps": 1000,
"children": {}
},
"Bad Tower": {
"latitude": "not-a-number",
"longitude": 999,
"downloadBandwidthMbps": 1000,
"uploadBandwidthMbps": 1000,
"children": {}
}
});
let parsed = parse_network_json_from_value(raw);
let good = parsed
.nodes
.iter()
.find(|n| n.name == "Tower A")
.expect("Tower A must be present");
let bad = parsed
.nodes
.iter()
.find(|n| n.name == "Bad Tower")
.expect("Bad Tower must be present");
assert!((good.latitude.unwrap() - 45.123).abs() < 0.001);
assert!((good.longitude.unwrap() + 111.75).abs() < 0.001);
assert_eq!(bad.latitude, None);
assert_eq!(bad.longitude, None);
let encoded = serde_json::to_value(good.clone_to_transit()).expect("transport serializes");
let encoded_lat = encoded["latitude"].as_f64().expect("latitude encodes as number");
let encoded_lon = encoded["longitude"].as_f64().expect("longitude encodes as number");
assert!((encoded_lat - 45.123).abs() < 0.001);
assert!((encoded_lon + 111.75).abs() < 0.001);
}
}
@@ -13,6 +13,9 @@ pub struct NetworkJsonNode {
/// The node name, as it appears in `network.json`
pub name: String,
/// Optional stable node identifier from `network.json` metadata.
pub id: Option<String>,
/// If true, this node is "virtual" (logical only): it exists for monitoring/aggregation
/// but should be omitted from the physical HTB tree.
pub virtual_node: bool,
@@ -59,6 +62,12 @@ pub struct NetworkJsonNode {
/// The node type
pub node_type: Option<String>,
/// Optional geographic latitude carried in `network.json` metadata.
pub latitude: Option<f32>,
/// Optional geographic longitude carried in `network.json` metadata.
pub longitude: Option<f32>,
/// Rolling per-site TemporalHeatmap (optional, allocated when enabled).
pub heatmap: Option<TemporalHeatmap>,
@@ -96,6 +105,7 @@ impl NetworkJsonNode {
NetworkJsonTransport {
name: self.name.clone(),
id: self.id.clone(),
is_virtual: self.virtual_node,
max_throughput: self.max_throughput,
current_throughput: (
@@ -129,6 +139,8 @@ impl NetworkJsonNode {
parents: self.parents.clone(),
immediate_parent: self.immediate_parent,
node_type: self.node_type.clone(),
latitude: self.latitude,
longitude: self.longitude,
}
}
}
@@ -8,6 +8,9 @@ use serde::{Deserialize, Serialize};
pub struct NetworkJsonTransport {
/// Display name
pub name: String,
/// Optional stable node identifier carried in network.json metadata.
#[serde(default)]
pub id: Option<String>,
/// True if this node is a "virtual" (logical-only) node.
#[serde(rename = "virtual", default)]
pub is_virtual: bool,
@@ -43,4 +46,10 @@ pub struct NetworkJsonTransport {
/// The type of node (site, ap, etc.)
#[serde(rename = "type")]
pub node_type: Option<String>,
/// Optional node latitude from network.json metadata.
#[serde(default)]
pub latitude: Option<f32>,
/// Optional node longitude from network.json metadata.
#[serde(default)]
pub longitude: Option<f32>,
}
@@ -37,7 +37,7 @@ fn track_queues() {
} else {
(
read_named_queue_from_interface(&config.isp_interface(), download_class),
read_named_queue_from_interface(&config.internet_interface(), download_class),
read_named_queue_from_interface(&config.internet_interface(), upload_class),
)
};
@@ -8,9 +8,10 @@ help.js
unknown-ips.js
configuration.js
circuit.js
site_map.js
flow_map.js
all_tree_sankey.js
asn_explorer.js
asn_analysis.js
lts_trial.js
config_general.js
config_rtt.js
File diff suppressed because it is too large. Load diff
@@ -128,10 +128,10 @@ function mdToHtml(md){
const cells = rowMatch[1].split('|').map(c => c.trim());
bodyRows.push(cells);
}
let html = '<table class="table table-sm table-striped table-bordered">';
let html = '<div class="table-responsive lqos-table-wrap"><table class="lqos-table lqos-table-compact mb-0">';
html += '<thead><tr>' + headerCells.map(h => `<th>${h}</th>`).join('') + '</tr></thead>';
html += '<tbody>' + bodyRows.map(r => '<tr>' + r.map(c => `<td>${c}</td>`).join('') + '</tr>').join('') + '</tbody>';
html += '</table>';
html += '</table></div>';
out.push(html);
} else {
out.push(l);
@@ -19,6 +19,7 @@ import {CakeQueueLength} from "./graphs/cake_queue_length";
import {CakeTraffic} from "./graphs/cake_traffic";
import {CakeMarks} from "./graphs/cake_marks";
import {CakeDrops} from "./graphs/cake_drops";
import {getNodeIdMap, linkToTreeNode} from "./executive_utils";
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
@@ -40,7 +41,9 @@ let devicePings = [];
let flowSankey = null;
let funnelGraphs = {};
let funnelParents = [];
let funnelParentSignature = [];
let funnelInitialized = false;
let funnelParentNodeName = null;
let excludeRttToggle = null;
let excludeRttLastValue = false;
let excludeRttBusy = false;
@@ -61,6 +64,54 @@ function isElementVisible(el) {
return !!(el && el.offsetWidth > 0 && el.offsetHeight > 0);
}
function loadingBlockHtml(label, sizeClass = "") {
const size = sizeClass ? ` ${sizeClass}` : "";
return `<div class="lqos-loading-block${size}"><i class="fa fa-spinner fa-spin"></i><span>${label}</span></div>`;
}
function initTooltipsWithin(rootEl = document) {
if (typeof bootstrap === "undefined" || !bootstrap.Tooltip) {
return;
}
const elements = rootEl.querySelectorAll('[data-bs-toggle="tooltip"]');
elements.forEach((element) => {
if (bootstrap.Tooltip.getOrCreateInstance) {
bootstrap.Tooltip.getOrCreateInstance(element);
} else {
new bootstrap.Tooltip(element);
}
});
}
function applyParentNodeLink(parentNodeName) {
const parentNodeEl = document.getElementById("parentNode");
if (!parentNodeEl) {
return;
}
parentNodeEl.textContent = parentNodeName || "";
if (!parentNodeName) {
parentNodeEl.removeAttribute("href");
parentNodeEl.removeAttribute("title");
parentNodeEl.style.pointerEvents = "none";
return;
}
parentNodeEl.style.pointerEvents = "";
getNodeIdMap().then((nodeIdLookup) => {
const href = linkToTreeNode(parentNodeName, nodeIdLookup);
if (!href) {
parentNodeEl.removeAttribute("href");
parentNodeEl.removeAttribute("title");
parentNodeEl.style.pointerEvents = "none";
return;
}
parentNodeEl.href = href;
parentNodeEl.title = `Open ${parentNodeName} in Tree`;
});
}
function resizeGraphIfVisible(graph) {
if (!graph || !graph.chart || typeof graph.chart.resize !== "function") {
return;
@@ -97,6 +148,130 @@ function resizeFunnelGraphs() {
});
}
function arrayEquals(a, b) {
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
function resolveFunnelState(msg, parentNode) {
const data = msg && msg.data ? msg.data : [];
const namedEntry = data.find((node) => node[1] && node[1].name === parentNode);
if (!namedEntry) {
return null;
}
const immediateParent = namedEntry[1];
const parentIndexes = Array.isArray(immediateParent.parents) ? [...immediateParent.parents] : [];
const parentSignature = parentIndexes.map((parent) => {
const node = data[parent] && data[parent][1] ? data[parent][1] : null;
if (!node) {
return `${parent}:missing`;
}
return `${parent}:${node.name}:${node.is_virtual === true ? "virtual" : "physical"}`;
});
return {
data,
immediateParent,
parentIndexes,
parentSignature,
};
}
function renderFunnel(state) {
const target = document.getElementById("theFunnel");
if (!target) {
return;
}
if (!state) {
funnelGraphs = {};
funnelParents = [];
funnelParentSignature = [];
clearDiv(target);
target.appendChild(document.createTextNode("No parent node found"));
return;
}
const parentIndexes = [...state.parentIndexes].reverse();
let parentDiv = document.createElement("div");
parentIndexes.forEach((parent) => {
const node = state.data[parent] && state.data[parent][1] ? state.data[parent][1] : null;
if (!node) {
return;
}
let row = document.createElement("div");
row.classList.add("row");
let col = document.createElement("div");
col.classList.add("col-12");
let heading = document.createElement("h5");
heading.classList.add("redactable");
const virtualLabel = node.is_virtual === true ? " <span class='badge text-bg-secondary ms-2'>Virtual</span>" : "";
heading.innerHTML = "<i class='fa fa-sitemap'></i> " + node.name + virtualLabel;
col.appendChild(heading);
row.appendChild(col);
parentDiv.appendChild(row);
row = document.createElement("div");
row.classList.add("row");
let col_tp = document.createElement("div");
col_tp.classList.add("col-4");
col_tp.id = "funnel_tp_" + parent;
col_tp.style.height = "250px";
row.appendChild(col_tp);
let col_rxmit = document.createElement("div");
col_rxmit.classList.add("col-4");
col_rxmit.id = "funnel_rxmit_" + parent;
row.appendChild(col_rxmit);
let col_rtt = document.createElement("div");
col_rtt.classList.add("col-4");
col_rtt.id = "funnel_rtt_" + parent;
row.appendChild(col_rtt);
parentDiv.appendChild(row);
});
funnelGraphs = {};
clearDiv(target);
target.appendChild(parentDiv);
requestAnimationFrame(() => {
setTimeout(() => {
parentIndexes.forEach((parent) => {
if (!document.getElementById("funnel_tp_" + parent)) {
return;
}
let tpGraph = new CircuitTotalGraph("funnel_tp_" + parent, "Throughput");
let rxmitGraph = new CircuitRetransmitGraph("funnel_rxmit_" + parent, "Retransmits");
let rttGraph = new WindowedLatencyHistogram("funnel_rtt_" + parent, "Latency Histogram", 300000);
funnelGraphs[parent] = {
tp: tpGraph,
rxmit: rxmitGraph,
rtt: rttGraph,
};
resizeGraphIfVisible(tpGraph);
resizeGraphIfVisible(rxmitGraph);
resizeGraphIfVisible(rttGraph);
});
}, 0);
});
funnelParents = state.parentIndexes;
funnelParentSignature = state.parentSignature;
}
function updateCakeTabAvailability(msg) {
try {
const kindDown = (msg?.kind_down || "").toLowerCase();
@@ -116,14 +291,8 @@ function updateCakeTabAvailability(msg) {
if (tabLi) tabLi.style.display = "";
if (tabContent) tabContent.style.display = "";
let displayKind = "Shaper Overview";
if (kindDown === "cake" || kindUp === "cake") {
displayKind = "CAKE Shaper Overview";
} else if (kindDown === "fq_codel" || kindUp === "fq_codel") {
displayKind = "fq_codel Shaper Overview";
}
if (tabBtn) {
tabBtn.innerHTML = '<i class="fa fa-birthday-cake"></i> ' + displayKind;
tabBtn.innerHTML = '<i class="fa fa-birthday-cake"></i> Queue Stats';
}
} catch (e) {
// Ignore label updates; data updates still continue.
@@ -158,9 +327,11 @@ function renderCakeGraphShell() {
</div>
<div class="col-3">
Queue Memory: <span id="cakeQueueMemory">?</span>
<div class="text-muted small mt-1">Queue Type: <span id="cakeQueueType">?</span></div>
</div>
</div>
`;
setQueueTypeDisplay("");
}
function applyCakeMessage(msg) {
@@ -254,6 +425,36 @@ function ipToString(ip) {
return String(ip);
}
function parseDirectionalSqmToken(token) {
const raw = (token ?? "").toString().trim().toLowerCase();
if (!raw) {
return { down: "", up: "" };
}
if (!raw.includes("/")) {
return { down: raw, up: raw };
}
const [down, up] = raw.split("/", 2);
return {
down: (down ?? "").toString().trim(),
up: (up ?? "").toString().trim(),
};
}
function formatQueueTypeDisplay(sqmToken) {
const { down, up } = parseDirectionalSqmToken(sqmToken);
const downLabel = down || "Unknown";
const upLabel = up || down || "Unknown";
return `${downLabel} / ${upLabel}`;
}
function setQueueTypeDisplay(sqmToken) {
const queueTypeEl = document.getElementById("cakeQueueType");
if (!queueTypeEl) {
return;
}
queueTypeEl.textContent = formatQueueTypeDisplay(sqmToken);
}
function requestCircuitById(onSuccess, onError) {
listenOnce("CircuitByIdResult", (msg) => {
if (!msg || !msg.ok) {
@@ -410,12 +611,21 @@ function connectFlowChannel() {
}
}, (msg) => {
latestFlowMsg = msg;
$("#activeFlowCount").text(Array.isArray(msg?.flows) ? msg.flows.length : 0);
applyFlowSankeyMessage(msg);
updateTrafficTab(msg);
});
}
function initFlowFilters() {
const hideSmallFlows = document.getElementById("hideSmallFlows");
if (!hideSmallFlows) {
return;
}
hideSmallFlows.addEventListener("change", () => {
updateTrafficTab(latestFlowMsg || { flows: [] });
});
}
let movingAverages = new Map();
let prevFlowBytes = new Map();
let tickCount = 0;
@@ -464,9 +674,13 @@ function formatRttPair(p50Nanos, p95Nanos) {
function updateTrafficTab(msg) {
let target = document.getElementById("allTraffic");
let visibleRowCount = 0;
let tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
let table = document.createElement("table");
table.classList.add("table", "table-sm", "table-striped");
table.classList.add("lqos-table", "lqos-table-tight");
let thead = document.createElement("thead", "small");
thead.style.fontSize = "0.8em";
@@ -601,7 +815,7 @@ function updateTrafficTab(msg) {
const excludeBtn = document.createElement("button");
excludeBtn.type = "button";
excludeBtn.className = "btn btn-outline-secondary btn-sm";
excludeBtn.textContent = "Exclude RTT…";
excludeBtn.textContent = "Exclude";
excludeBtn.disabled = !remoteIp;
excludeBtn.title = "Open a wizard to exclude RTT samples for this remote IP/CIDR (requires saving in Flow Tracking config).";
excludeBtn.addEventListener("click", (e) => {
@@ -669,6 +883,7 @@ function updateTrafficTab(msg) {
// Render the sorted table
tableRows.forEach((rowData) => {
if (!rowData.visible) return;
visibleRowCount++;
let row = document.createElement("tr");
row.classList.add("small");
@@ -697,8 +912,11 @@ function updateTrafficTab(msg) {
table.appendChild(tbody);
tableWrap.appendChild(table);
clearDiv(target);
target.appendChild(table);
target.appendChild(tableWrap);
$("#trafficFlowCount").text(visibleRowCount);
return visibleRowCount;
}
function updateSpeedometer(devices) {
@@ -769,12 +987,10 @@ function fillLiveDevices(devices) {
const curP95 = device.rtt_current_p95_nanos || {};
const totP50 = device.rtt_total_p50_nanos || {};
const totP95 = device.rtt_total_p95_nanos || {};
rttDown.innerHTML =
"<div class='tiny'>C: " +
formatRttPair(curP50.down, curP95.down) +
"</div><div class='tiny text-secondary'>T: " +
formatRttPair(totP50.down, totP95.down) +
"</div>";
rttDown.innerHTML = formatRttMetricBlock(
formatRttPair(curP50.down, curP95.down),
formatRttPair(totP50.down, totP95.down)
);
}
if (rttUp !== null) {
@@ -782,12 +998,10 @@ function fillLiveDevices(devices) {
const curP95 = device.rtt_current_p95_nanos || {};
const totP50 = device.rtt_total_p50_nanos || {};
const totP95 = device.rtt_total_p95_nanos || {};
rttUp.innerHTML =
"<div class='tiny'>C: " +
formatRttPair(curP50.up, curP95.up) +
"</div><div class='tiny text-secondary'>T: " +
formatRttPair(totP50.up, totP95.up) +
"</div>";
rttUp.innerHTML = formatRttMetricBlock(
formatRttPair(curP50.up, curP95.up),
formatRttPair(totP50.up, totP95.up)
);
}
if (tcp_retransmitsDown !== null) {
@@ -812,6 +1026,19 @@ function fillLiveDevices(devices) {
});
}
function formatRttMetricBlock(currentText, totalText) {
return "<div class='lqos-rtt-metric'>" +
"<div class='lqos-rtt-metric-line'>" +
"<span class='lqos-rtt-metric-label'>C:</span>" +
"<span class='lqos-rtt-metric-value'>" + currentText + "</span>" +
"</div>" +
"<div class='lqos-rtt-metric-line text-secondary'>" +
"<span class='lqos-rtt-metric-label'>T:</span>" +
"<span class='lqos-rtt-metric-value'>" + totalText + "</span>" +
"</div>" +
"</div>";
}
function initialDevices(circuits) {
let target = document.getElementById("devices");
clearDiv(target);
@@ -836,16 +1063,21 @@ function initialDevices(circuits) {
name.innerHTML = "<i class='fa fa-computer'></i> " + circuit.device_name;
d.appendChild(name);
let infoTableWrap = document.createElement("div");
infoTableWrap.classList.add("lqos-table-wrap");
let infoTable = document.createElement("table");
infoTable.classList.add("table", "table-sm", "table-striped");
infoTable.classList.add("lqos-table", "lqos-table-tight");
let tbody = document.createElement("tbody");
// MAC Row
let tr = document.createElement("tr");
let td = document.createElement("td");
td.innerHTML = "<b>MAC Address</b>";
td.textContent = "MAC Address";
td.classList.add("table-label-cell");
tr.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.classList.add("redactable");
td.colSpan = 2;
td.innerHTML = circuit.mac;
@@ -855,9 +1087,11 @@ function initialDevices(circuits) {
// Comment Row
let tr2 = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = "<b>Comment</b>";
td.textContent = "Comment";
td.classList.add("table-label-cell");
tr2.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.colSpan = 2;
td.innerHTML = circuit.comment;
tr2.appendChild(td);
@@ -866,12 +1100,14 @@ function initialDevices(circuits) {
// IPv4 Row
let tr3 = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = "<b>IPv4 Address(es)</b>";
td.textContent = "IPv4 Address(es)";
td.classList.add("table-label-cell");
tr3.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.colSpan = 2;
let ipv4Table = document.createElement("table");
ipv4Table.classList.add("table", "table-sm");
ipv4Table.classList.add("lqos-table", "lqos-table-tight");
let ipv4Body = document.createElement("tbody");
circuit.ipv4.forEach((ip) => {
const ipStr = ipToString(ip[0]);
@@ -903,13 +1139,15 @@ function initialDevices(circuits) {
// IPv6 Row
let tr4 = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = "<b>IPv6 Address(es)</b>";
td.textContent = "IPv6 Address(es)";
td.classList.add("table-label-cell");
tr4.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.colSpan = 2;
let ipv6 = document.createElement("table");
ipv6.classList.add("table", "table-sm");
ipv6.classList.add("lqos-table", "lqos-table-tight");
let ipv6Body = document.createElement("tbody");
circuit.ipv6.forEach((ip) => {
const ipStr = ipToString(ip[0]);
@@ -947,9 +1185,11 @@ function initialDevices(circuits) {
// Placeholder for Last Seen
let tr8 = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = "<b>Last Seen</b>";
td.textContent = "Last Seen";
td.classList.add("table-label-cell");
tr8.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.colSpan = 2;
td.id = "last_seen_" + circuit.device_id;
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
@@ -959,13 +1199,16 @@ function initialDevices(circuits) {
// Placeholder for throughput
let tr5 = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = "<b>Throughput</b>";
td.textContent = "Throughput";
td.classList.add("table-label-cell");
tr5.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.id = "throughputDown_" + circuit.device_id;
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
tr5.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.id = "throughputUp_" + circuit.device_id;
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
tr5.appendChild(td);
@@ -974,35 +1217,42 @@ function initialDevices(circuits) {
// Placeholder for RTT
let tr6 = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = "<b>RTT P50/P95</b>";
td.textContent = "RTT P50/P95";
td.classList.add("table-label-cell");
tr6.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell", "lqos-rtt-metric-cell");
td.id = "rttDown_" + circuit.device_id;
td.innerHTML = "<span class='text-secondary'>Sampling...</span>";
td.innerHTML = formatRttMetricBlock("Sampling...", "Sampling...");
tr6.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell", "lqos-rtt-metric-cell");
td.id = "rttUp_" + circuit.device_id;
td.innerHTML = "<span class='text-secondary'>Sampling...</span>";
td.innerHTML = formatRttMetricBlock("Sampling...", "Sampling...");
tr6.appendChild(td);
tbody.appendChild(tr6);
// Placeholder for TCP Retransmits
let tr7 = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = "<b>TCP Re-Xmits</b>";
td.textContent = "TCP Re-Xmits";
td.classList.add("table-label-cell");
tr7.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.id = "tcp_retransmitsDown_" + circuit.device_id;
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
tr7.appendChild(td);
td = document.createElement("td");
td.classList.add("table-value-cell");
td.id = "tcp_retransmitsUp_" + circuit.device_id;
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
tr7.appendChild(td);
tbody.appendChild(tr7);
infoTable.appendChild(tbody);
d.appendChild(infoTable);
infoTableWrap.appendChild(infoTable);
d.appendChild(infoTableWrap);
// Graph container (2x2)
let graphCol = document.createElement("div");
@@ -1019,7 +1269,7 @@ function initialDevices(circuits) {
let div = document.createElement("div");
div.id = divId;
div.style.height = "250px";
div.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
div.innerHTML = loadingBlockHtml("Loading chart…", "lqos-loading-block-sm");
col.appendChild(div);
graphRow.appendChild(col);
deviceGraphs[divId] = graphFactory(divId);
@@ -1034,92 +1284,37 @@ function initialDevices(circuits) {
}
function initialFunnel(parentNode) {
let target = document.getElementById("theFunnel");
funnelParentNodeName = parentNode;
listenOnce("NetworkTree", (msg) => {
const data = msg && msg.data ? msg.data : [];
let immediateParent = null;
data.forEach((node) => {
if (node[1].name === parentNode) {
immediateParent = node[1];
}
});
if (immediateParent === null) {
clearDiv(target);
target.appendChild(document.createTextNode("No parent node found"));
return;
renderFunnel(resolveFunnelState(msg, parentNode));
if (funnelSubscription) {
funnelSubscription.dispose();
}
let parentDiv = document.createElement("div");
immediateParent.parents.reverse().forEach((parent) => {
//console.log(data[parent]);
let row = document.createElement("div");
row.classList.add("row");
let col = document.createElement("div");
col.classList.add("col-12");
let heading = document.createElement("h5");
heading.classList.add("redactable");
heading.innerHTML = "<i class='fa fa-sitemap'></i> " + data[parent][1].name;
col.appendChild(heading);
row.appendChild(col);
parentDiv.appendChild(row);
// Row for graphs
row = document.createElement("div");
row.classList.add("row");
let col_tp = document.createElement("div");
col_tp.classList.add("col-4");
col_tp.id = "funnel_tp_" + parent;
col_tp.style.height = "250px";
row.appendChild(col_tp);
let col_rxmit = document.createElement("div");
col_rxmit.classList.add("col-4");
col_rxmit.id = "funnel_rxmit_" + parent;
row.appendChild(col_rxmit);
let col_rtt = document.createElement("div");
col_rtt.classList.add("col-4");
col_rtt.id = "funnel_rtt_" + parent;
row.appendChild(col_rtt);
parentDiv.appendChild(row);
});
clearDiv(target);
target.appendChild(parentDiv);
// Ugly hack to defer until the DOM is updated
requestAnimationFrame(() => {setTimeout(() => {
immediateParent.parents.reverse().forEach((parent) => {
let tpGraph = new CircuitTotalGraph("funnel_tp_" + parent, "Throughput");
let rxmitGraph = new CircuitRetransmitGraph("funnel_rxmit_" + parent, "Retransmits");
let rttGraph = new WindowedLatencyHistogram("funnel_rtt_" + parent, "Latency Histogram", 300000);
funnelGraphs[parent] = {
tp: tpGraph,
rxmit: rxmitGraph,
rtt: rttGraph,
};
resizeGraphIfVisible(tpGraph);
resizeGraphIfVisible(rxmitGraph);
resizeGraphIfVisible(rttGraph);
});
funnelParents = immediateParent.parents;
if (funnelSubscription) {
funnelSubscription.dispose();
}
funnelSubscription = subscribeWS(["NetworkTree"], onTreeEvent);
}, 0)});
funnelSubscription = subscribeWS(["NetworkTree"], onTreeEvent);
});
wsClient.send({ NetworkTree: {} });
}
function onTreeEvent(msg) {
//console.log(msg);
if (msg.event !== "NetworkTree" || !funnelParentNodeName) {
return;
}
const state = resolveFunnelState(msg, funnelParentNodeName);
const nextParents = state ? state.parentIndexes : [];
const nextSignature = state ? state.parentSignature : [];
const shouldRebuild =
!arrayEquals(nextParents, funnelParents) ||
!arrayEquals(nextSignature, funnelParentSignature);
if (shouldRebuild) {
renderFunnel(state);
}
funnelParents.forEach((parent) => {
if (msg.event !== "NetworkTree") return;
let myMessage = msg.data[parent][1];
if (myMessage === undefined) return;
const nodeEntry = msg.data[parent];
if (!nodeEntry || !nodeEntry[1] || !funnelGraphs[parent]) return;
let myMessage = nodeEntry[1];
let tpGraph = funnelGraphs[parent].tp;
let rxmitGraph = funnelGraphs[parent].rxmit;
let rttGraph = funnelGraphs[parent].rtt;
@@ -1261,17 +1456,21 @@ function download(dataurl, filename) {
}
function loadInitial() {
initTooltipsWithin(document);
initExcludeRttToggle();
initFlowFilters();
requestCircuitById((circuits) => {
let circuit = circuits[0];
$("#circuitName").text(circuit.circuit_name);
$("#parentNode").text(circuit.parent_node);
$("#circuitName").attr("title", circuit.circuit_name || "");
applyParentNodeLink(circuit.parent_node);
$("#bwMax").text(formatMbps(circuit.download_max_mbps) + " / " + formatMbps(circuit.upload_max_mbps));
$("#bwMin").text(formatMbps(circuit.download_min_mbps) + " / " + formatMbps(circuit.upload_min_mbps));
plan = {
down: toNumber(circuit.download_max_mbps, 0),
up: toNumber(circuit.upload_max_mbps, 0),
};
setQueueTypeDisplay(circuit.sqm_override || "");
initialDevices(circuits);
speedometer = new BitsPerSecondGauge("bitsGauge", "Plan");
qooGauge = new QooScoreGauge("qooGauge");
@@ -279,6 +279,7 @@ export function renderConfigMenu(currentPage) {
{ href: "config_interface.html", icon: "fa-chain", text: "Network Mode", id: "interface" },
{ href: "config_queues.html", icon: "fa-car", text: "Queues", id: "queues" },
{ href: "config_stormguard.html", icon: "fa-bolt", text: "StormGuard", id: "stormguard" },
{ href: "config_treeguard.html", icon: "fa-shield-halved", text: "TreeGuard", id: "treeguard" },
{ href: "config_lts.html", icon: "fa-line-chart", text: "LibreQoS Insight", id: "lts" },
{ href: "config_iprange.html", icon: "fa-address-card", text: "IP Ranges", id: "iprange" },
{ href: "config_flows.html", icon: "fa-arrow-circle-down", text: "Flow Tracking", id: "flows" },
@@ -91,7 +91,8 @@ function loadUsers() {
return;
}
const table = $('<table class="table table-striped">')
const tableWrap = $('<div class="table-responsive lqos-table-wrap">');
const table = $('<table class="lqos-table lqos-table-compact mb-0">')
.append('<thead><tr><th>Username</th><th>Role</th><th>Actions</th></tr></thead>');
const tbody = $('<tbody>');
@@ -112,7 +113,8 @@ function loadUsers() {
});
table.append(tbody);
userList.append(table);
tableWrap.append(table);
userList.append(tableWrap);
// Attach edit handlers
$('.edit-user').on('click', function() {
@@ -78,8 +78,10 @@ function renderSummary(data) {
const right = document.createElement('div');
right.className = 'col-12 col-lg-4';
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
const table = document.createElement("table");
table.classList.add("table", "table-striped", "table-sm");
table.classList.add("lqos-table", "lqos-table-compact");
const thead = document.createElement("thead");
thead.appendChild(theading("CPU"));
thead.appendChild(theading("DL Circuits"));
@@ -128,7 +130,8 @@ function renderSummary(data) {
});
table.appendChild(tbody);
left.appendChild(table);
tableWrap.appendChild(table);
left.appendChild(tableWrap);
// Right column pie chart (DL Max per CPU)
const chartDiv = document.createElement('div');
@@ -205,8 +208,10 @@ function renderCircuits(page) {
smallNote.innerText = `Total: ${page.total.toLocaleString()} circuits`;
target.appendChild(smallNote);
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
const table = document.createElement("table");
table.classList.add("table", "table-striped", "table-sm");
table.classList.add("lqos-table", "lqos-table-compact");
const thead = document.createElement("thead");
thead.appendChild(theading("Circuit ID"));
thead.appendChild(theading("Circuit Name"));
@@ -249,7 +254,8 @@ function renderCircuits(page) {
});
table.appendChild(tbody);
target.appendChild(table);
tableWrap.appendChild(table);
target.appendChild(tableWrap);
}
function fetchSummary() {
@@ -56,7 +56,7 @@ export class CircuitCapacityDash extends BaseDashlet {
const cached = this.timeCache.get();
let table = document.createElement("table");
table.classList.add("dash-table", "table-sm", "small");
table.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
let thead = document.createElement("thead");
thead.classList.add("small");
thead.appendChild(theading("Circuit"));
@@ -101,4 +101,4 @@ export class CircuitCapacityDash extends BaseDashlet {
target.appendChild(table);
}
}
}
}
@@ -26,8 +26,7 @@ export class Top10EndpointsByCountry extends DashletBaseInsight {
buildContainer() {
let base = super.buildContainer();
base.style.height = "250px";
base.style.overflow = "auto";
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
return base;
}
@@ -100,4 +99,4 @@ export class Top10EndpointsByCountry extends DashletBaseInsight {
target.appendChild(t);
}
}
}
}
@@ -39,7 +39,7 @@ export class EtherProtocols extends DashletBaseInsight {
let target = document.getElementById(this.id);
let t = document.createElement("table");
t.classList.add("dash-table", "table-sm", "small");
t.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
let th = document.createElement("thead");
th.classList.add("small");
@@ -81,4 +81,4 @@ export class EtherProtocols extends DashletBaseInsight {
target.appendChild(t);
}
}
}
}
@@ -39,7 +39,7 @@ export class IpProtocols extends DashletBaseInsight {
let target = document.getElementById(this.id);
let t = document.createElement("table");
t.classList.add("dash-table", "table-sm", "small");
t.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
let th = document.createElement("thead");
th.classList.add("small");
@@ -69,4 +69,4 @@ export class IpProtocols extends DashletBaseInsight {
target.appendChild(t);
}
}
}
}
@@ -57,7 +57,7 @@ export class LtsShaperStatus extends BaseDashlet {
let target = document.getElementById(this.contentId);
let table = document.createElement("table");
table.classList.add("table", "table-sm", "small");
table.classList.add("lqos-table", "lqos-table-compact", "small");
let thead = document.createElement("thead");
thead.appendChild(theading(""));
thead.appendChild(theading("Shaper"));
@@ -81,7 +81,10 @@ export class LtsShaperStatus extends BaseDashlet {
})
table.appendChild(tbody);
clearDiv(target);
target.appendChild(table);
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
tableWrap.appendChild(table);
target.appendChild(tableWrap);
//console.log(data);
});
@@ -36,8 +36,7 @@ export class Top10Downloaders extends DashletBaseInsight {
buildContainer() {
let base = super.buildContainer();
base.style.height = "250px";
base.style.overflow = "auto";
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
return base;
}
@@ -82,7 +81,7 @@ export class Top10Downloaders extends DashletBaseInsight {
let target = document.getElementById(this.id);
let table = document.createElement("table");
table.classList.add("table", "table-sm", "small");
table.classList.add("lqos-table", "lqos-table-compact", "small");
let thead = document.createElement("thead");
thead.appendChild(theading("Circuit"));
thead.appendChild(theading("Bytes Downloaded"));
@@ -111,7 +110,10 @@ export class Top10Downloaders extends DashletBaseInsight {
})
table.appendChild(tbody);
clearDashDiv(this.id, target);
target.appendChild(table);
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
tableWrap.appendChild(table);
target.appendChild(tableWrap);
});
wsClient.send({ LtsTop10Downloaders: { seconds } });
}
@@ -42,8 +42,7 @@ export class Top10FlowsBytes extends DashletBaseInsight {
buildContainer() {
let base = super.buildContainer();
base.style.height = "250px";
base.style.overflow = "auto";
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
return base;
}
@@ -57,7 +56,7 @@ export class Top10FlowsBytes extends DashletBaseInsight {
let target = document.getElementById(this.id);
let t = document.createElement("table");
t.classList.add("dash-table", "table-sm", "small");
t.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
let th = document.createElement("thead");
th.classList.add("small");
@@ -68,7 +67,9 @@ export class Top10FlowsBytes extends DashletBaseInsight {
th.appendChild(theading("Total"));
th.appendChild(theading("RTT", 2));
th.appendChild(theading("TCP Retransmits", 2));
th.appendChild(theading("Remote ASN"));
const asnHeading = theading("Remote ASN");
asnHeading.classList.add("lqos-asn-cell");
th.appendChild(asnHeading);
t.appendChild(th);
let tbody = document.createElement("tbody");
@@ -138,13 +139,16 @@ export class Top10FlowsBytes extends DashletBaseInsight {
row.appendChild(tcp2);
let asn = document.createElement("td");
asn.innerText = r.remote_asn_name;
if (asn.innerText === "") {
asn.innerText = r.remote_ip;
}
if (asn.innerText.length > 13) {
asn.classList.add("tiny");
asn.classList.add("lqos-asn-cell");
const asnLabel = (r.remote_asn_name && r.remote_asn_name.length > 0) ? r.remote_asn_name : r.remote_ip;
const asnText = document.createElement("span");
asnText.classList.add("lqos-table-cell-ellipsis");
if (asnLabel && asnLabel.length > 13) {
asnText.classList.add("tiny");
}
asnText.textContent = asnLabel || "";
asnText.title = asnLabel || "";
asn.appendChild(asnText);
row.appendChild(asn);
t.appendChild(row);
@@ -170,7 +174,7 @@ export class Top10FlowsBytes extends DashletBaseInsight {
let target = document.getElementById(this.id);
let table = document.createElement("table");
table.classList.add("table", "table-sm", "small");
table.classList.add("lqos-table", "lqos-table-compact", "small");
let thead = document.createElement("thead");
thead.appendChild(theading("Circuit"));
thead.appendChild(theading("Protocol"));
@@ -180,7 +184,9 @@ export class Top10FlowsBytes extends DashletBaseInsight {
thead.appendChild(theading("RTT UL"));
thead.appendChild(theading("Rxmits DL"));
thead.appendChild(theading("Rxmits UL"));
thead.appendChild(theading("ASN"));
const asnHeading = theading("ASN");
asnHeading.classList.add("lqos-asn-cell");
thead.appendChild(asnHeading);
table.appendChild(thead);
let tbody = document.createElement("tbody");
@@ -211,16 +217,26 @@ export class Top10FlowsBytes extends DashletBaseInsight {
} else {
tr.appendChild(simpleRowHtml(formatRetransmit(row.rxmit_up)));
}
if (row.asn_name === null) {
row.asn_name = "-";
} else {
tr.appendChild(simpleRow(row.asn_name));
const asnCell = document.createElement("td");
asnCell.classList.add("lqos-asn-cell");
const asnText = document.createElement("span");
const asnLabel = row.asn_name === null ? "-" : row.asn_name;
asnText.classList.add("lqos-table-cell-ellipsis");
if (asnLabel && asnLabel.length > 13) {
asnText.classList.add("tiny");
}
asnText.textContent = asnLabel || "";
asnText.title = asnLabel || "";
asnCell.appendChild(asnText);
tr.appendChild(asnCell);
tbody.appendChild(tr);
})
table.appendChild(tbody);
clearDashDiv(this.id, target);
target.appendChild(table);
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
tableWrap.appendChild(table);
target.appendChild(tableWrap);
});
wsClient.send({ LtsTopFlows: { seconds } });
}
@@ -31,8 +31,7 @@ export class Top10FlowsRate extends DashletBaseInsight {
buildContainer() {
let base = super.buildContainer();
base.style.height = "250px";
base.style.overflow = "auto";
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
return base;
}
@@ -56,7 +55,9 @@ export class Top10FlowsRate extends DashletBaseInsight {
th.appendChild(theading("Total"));
th.appendChild(theading("RTT", 2));
th.appendChild(theading("TCP Retransmits", 2));
th.appendChild(theading("Remote ASN"));
const asnHeading = theading("Remote ASN");
asnHeading.classList.add("lqos-asn-cell");
th.appendChild(asnHeading);
t.appendChild(th);
let tbody = document.createElement("tbody");
@@ -126,13 +127,16 @@ export class Top10FlowsRate extends DashletBaseInsight {
row.appendChild(tcp2);
let asn = document.createElement("td");
asn.innerText = r.remote_asn_name;
if (asn.innerText === "") {
asn.innerText = r.remote_ip;
}
if (asn.innerText.length > 13) {
asn.classList.add("tiny");
asn.classList.add("lqos-asn-cell");
const asnLabel = (r.remote_asn_name && r.remote_asn_name.length > 0) ? r.remote_asn_name : r.remote_ip;
const asnText = document.createElement("span");
asnText.classList.add("lqos-table-cell-ellipsis");
if (asnLabel && asnLabel.length > 13) {
asnText.classList.add("tiny");
}
asnText.textContent = asnLabel || "";
asnText.title = asnLabel || "";
asn.appendChild(asnText);
row.appendChild(asn);
t.appendChild(row);
@@ -35,7 +35,7 @@ export class TopTreeSummary extends DashletBaseInsight {
let t = document.createElement("table");
t.id = this.id + "_table";
t.classList.add("dash-table", "table-sm", "mytable", "small");
t.classList.add("dash-table", "lqos-table", "lqos-table-compact", "mytable", "small");
let th = document.createElement("thead");
th.classList.add('small');
@@ -40,7 +40,7 @@ export class TreeCapacityDash extends DashletBaseInsight {
let target = document.getElementById(this.id);
let table = document.createElement("table");
table.classList.add("dash-table", "table-sm", "small");
table.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
let thead = document.createElement("thead");
thead.classList.add("small");
thead.appendChild(theading("Node"));
@@ -227,8 +227,11 @@ export class TreeGuardActivityDashlet extends BaseDashlet {
const wrap = document.createElement("div");
wrap.classList.add("p-2");
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
const table = document.createElement("table");
table.classList.add("table", "table-sm", "table-striped", "mb-0", "small");
table.classList.add("lqos-table", "lqos-table-compact", "mb-0", "small");
const thead = document.createElement("thead");
thead.classList.add("small");
@@ -243,7 +246,8 @@ export class TreeGuardActivityDashlet extends BaseDashlet {
this.tbody = document.createElement("tbody");
table.appendChild(thead);
table.appendChild(this.tbody);
wrap.appendChild(table);
tableWrap.appendChild(table);
wrap.appendChild(tableWrap);
base.appendChild(wrap);
return base;
}
@@ -243,17 +243,21 @@ export class TreeGuardStatusDashlet extends BaseDashlet {
const wrap = document.createElement("div");
wrap.classList.add("p-2");
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
const table = document.createElement("table");
table.classList.add("table", "table-sm", "mb-2");
table.classList.add("lqos-table", "lqos-table-compact", "mb-0");
const tbody = document.createElement("tbody");
const mkRow = (label, valueEl) => {
const tr = document.createElement("tr");
const tdL = document.createElement("td");
tdL.classList.add("text-muted");
tdL.classList.add("table-label-cell");
tdL.style.width = "45%";
tdL.textContent = label;
const tdV = document.createElement("td");
tdV.classList.add("table-value-cell");
tdV.appendChild(valueEl);
tr.appendChild(tdL);
tr.appendChild(tdV);
@@ -280,10 +284,11 @@ export class TreeGuardStatusDashlet extends BaseDashlet {
tbody.appendChild(mkRow("Last Action", this.lastActionEl));
table.appendChild(tbody);
wrap.appendChild(table);
tableWrap.appendChild(table);
wrap.appendChild(tableWrap);
const warningsHeader = document.createElement("div");
warningsHeader.classList.add("text-muted", "small", "mb-1");
warningsHeader.classList.add("text-muted", "small", "mt-3", "mb-1");
warningsHeader.textContent = "Warnings";
wrap.appendChild(warningsHeader);
@@ -40,8 +40,7 @@ export class Worst10Downloaders extends DashletBaseInsight {
buildContainer() {
let base = super.buildContainer();
base.style.height = "250px";
base.style.overflow = "auto";
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
return base;
}
@@ -82,7 +81,7 @@ export class Worst10Downloaders extends DashletBaseInsight {
let target = document.getElementById(this.id);
let table = document.createElement("table");
table.classList.add("table", "table-sm", "small");
table.classList.add("lqos-table", "lqos-table-compact", "small");
let thead = document.createElement("thead");
thead.appendChild(theading("Circuit"));
thead.appendChild(theading("Bytes Downloaded"));
@@ -111,7 +110,10 @@ export class Worst10Downloaders extends DashletBaseInsight {
})
table.appendChild(tbody);
clearDashDiv(this.id, target);
target.appendChild(table);
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
tableWrap.appendChild(table);
target.appendChild(tableWrap);
});
wsClient.send({ LtsWorst10Rtt: { seconds } });
}
@@ -40,8 +40,7 @@ export class Worst10Retransmits extends DashletBaseInsight {
buildContainer() {
let base = super.buildContainer();
base.style.height = "250px";
base.style.overflow = "auto";
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
return base;
}
@@ -82,7 +81,7 @@ export class Worst10Retransmits extends DashletBaseInsight {
let target = document.getElementById(this.id);
let table = document.createElement("table");
table.classList.add("table", "table-sm", "small");
table.classList.add("lqos-table", "lqos-table-compact", "small");
let thead = document.createElement("thead");
thead.appendChild(theading("Circuit"));
thead.appendChild(theading("Bytes Downloaded"));
@@ -111,7 +110,10 @@ export class Worst10Retransmits extends DashletBaseInsight {
})
table.appendChild(tbody);
clearDashDiv(this.id, target);
target.appendChild(table);
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
tableWrap.appendChild(table);
target.appendChild(tableWrap);
});
wsClient.send({ LtsWorst10Rxmit: { seconds } });
}
@@ -20,7 +20,7 @@ export function listenExecutiveHeatmaps(onData) {
let siteIdMap = null;
let siteIdMapPromise = null;
export function getSiteIdMap() {
export function getNodeIdMap() {
if (siteIdMap) return Promise.resolve(siteIdMap);
if (siteIdMapPromise) return siteIdMapPromise;
siteIdMapPromise = new Promise((resolve) => {
@@ -42,6 +42,7 @@ export function getSiteIdMap() {
map.set(node.name, id);
}
});
resolveOnce(map);
});
wsClient.send({ NetworkTree: {} });
setTimeout(() => {
@@ -51,6 +52,10 @@ export function getSiteIdMap() {
return siteIdMapPromise;
}
export function getSiteIdMap() {
return getNodeIdMap();
}
export function linkToCircuit(circuitId) {
if (!circuitId) return null;
return `circuit.html?id=${encodeURIComponent(circuitId)}`;
@@ -63,6 +68,13 @@ export function linkToSite(siteName, siteIdLookup) {
return `tree.html?parent=${encodeURIComponent(siteId)}`;
}
export function linkToTreeNode(nodeName, nodeIdLookup) {
if (!nodeName || !nodeIdLookup) return null;
const nodeId = nodeIdLookup.get(nodeName);
if (nodeId === undefined || nodeId === null) return null;
return `tree.html?parent=${encodeURIComponent(nodeId)}`;
}
export function renderCircuitLink(name, circuitId) {
const link = linkToCircuit(circuitId);
if (!link) return `<span class="redactable">${name}</span>`;
@@ -117,8 +129,8 @@ export function renderTable(targetId, columns, rows, emptyMessage) {
return `<tr>${cells}</tr>`;
}).join("");
target.innerHTML = `
<div class="table-responsive">
<table class="table table-sm table-striped align-middle mb-0">
<div class="table-responsive lqos-table-wrap">
<table class="lqos-table lqos-table-compact align-middle mb-0">
${thead}
<tbody>${tbody}</tbody>
</table>
File diff suppressed because it is too large. Load diff
@@ -177,7 +177,7 @@ export function topNTableRow(r) {
export function TopNTableFromMsgData(circuits) {
let t = document.createElement("table");
t.classList.add("table-sm", "dash-table");
t.classList.add("dash-table", "lqos-table", "lqos-table-compact");
t.appendChild(topNTableHeader());
@@ -187,4 +187,4 @@ export function TopNTableFromMsgData(circuits) {
});
t.appendChild(tbody);
return t;
}
}
@@ -112,11 +112,11 @@ export function colorByRttMs(rttMs, scale = null) {
export function colorByQoqScore(score0to100) {
// Distinguish "no data" from a real 0 score (which is legitimately bad).
if (score0to100 === null || score0to100 === undefined) {
return "var(--bs-border-color)";
return "#8893a5";
}
const raw = Number(score0to100);
if (!Number.isFinite(raw)) {
return "var(--bs-border-color)";
return "#8893a5";
}
const s = Math.min(100, Math.max(0, raw));
if (isColorBlindMode()) {
@@ -1,106 +1,114 @@
export function initDayNightMode() {
if (isDarkMode()) {
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.checked = true;
document.body.classList.add('dark-mode');
document.documentElement.setAttribute('data-bs-theme', "dark");
} else {
document.body.classList.remove('dark-mode');
document.documentElement.setAttribute('data-bs-theme', "light");
function getSavedTheme() {
const currentTheme = localStorage.getItem('theme');
if (currentTheme === 'dark' || currentTheme === 'light') {
return currentTheme;
}
return null;
}
function getEffectiveTheme() {
return getSavedTheme() || 'dark';
}
function setGraphPalette(theme) {
if (theme === 'dark') {
window.graphPalette = [
'#4992ff',
'#7cffb2',
'#fddd60',
'#ff6e76',
'#58d9f9',
'#05c091',
'#ff8a45',
'#8d48e3',
'#dd79ff'
];
return;
}
document.addEventListener('DOMContentLoaded', (event) => {
const darkModeSwitch = document.getElementById('darkModeSwitch');
const currentTheme = localStorage.getItem('theme');
window.graphPalette = [
'#d87c7c',
'#919e8b',
'#d7ab82',
'#6e7074',
'#61a0a8',
'#efa18d',
'#787464',
'#cc7e63',
'#724e58',
'#4b565b'
];
}
if (currentTheme === 'dark') {
document.body.classList.add('dark-mode');
darkModeSwitch.checked = true;
function applyTheme(theme, darkModeSwitch) {
const isDark = theme === 'dark';
if (darkModeSwitch) {
darkModeSwitch.checked = isDark;
}
document.body.classList.toggle('dark-mode', isDark);
document.documentElement.setAttribute('data-bs-theme', isDark ? 'dark' : 'light');
setGraphPalette(theme);
}
function reinitializeGraphs(theme) {
if (window.graphList === undefined) {
return;
}
const next = [];
window.graphList.forEach((graph) => {
if (!graph || !graph.dom) return;
if (typeof graph.dom.isConnected === "boolean" && !graph.dom.isConnected) {
// Drop charts whose DOM has been removed (e.g. closed zoom overlay).
try {
if (graph.chart && graph.chart.dispose) {
graph.chart.dispose();
}
} catch (_) {}
return;
}
// Defensive: dispose any existing instance before re-init.
try {
if (graph.chart && graph.chart.dispose) {
graph.chart.dispose();
} else if (typeof echarts !== "undefined" && echarts.getInstanceByDom) {
const existing = echarts.getInstanceByDom(graph.dom);
if (existing) existing.dispose();
}
} catch (_) {}
if (typeof echarts === "undefined" || !echarts.init) {
return;
}
graph.chart = echarts.init(graph.dom, theme === 'dark' ? 'dark' : 'vintage');
graph.chart.setOption(graph.option);
if (graph.onThemeChange) {
graph.onThemeChange();
}
next.push(graph);
});
window.graphList = next;
}
export function initDayNightMode() {
applyTheme(getEffectiveTheme(), document.getElementById('darkModeSwitch'));
document.addEventListener('DOMContentLoaded', () => {
const darkModeSwitch = document.getElementById('darkModeSwitch');
applyTheme(getEffectiveTheme(), darkModeSwitch);
darkModeSwitch.addEventListener('change', () => {
if (darkModeSwitch.checked) {
document.body.classList.add('dark-mode');
document.documentElement.setAttribute('data-bs-theme', "dark");
localStorage.setItem('theme', 'dark');
window.graphPalette = [
'#4992ff',
'#7cffb2',
'#fddd60',
'#ff6e76',
'#58d9f9',
'#05c091',
'#ff8a45',
'#8d48e3',
'#dd79ff'
];
} else {
document.body.classList.remove('dark-mode');
document.documentElement.setAttribute('data-bs-theme', "light");
localStorage.setItem('theme', 'light');
window.graphPalette = [
'#d87c7c',
'#919e8b',
'#d7ab82',
'#6e7074',
'#61a0a8',
'#efa18d',
'#787464',
'#cc7e63',
'#724e58',
'#4b565b'
];
}
if (window.graphList !== undefined) {
const next = [];
window.graphList.forEach((graph) => {
if (!graph || !graph.dom) return;
if (typeof graph.dom.isConnected === "boolean" && !graph.dom.isConnected) {
// Drop charts whose DOM has been removed (e.g. closed zoom overlay).
try {
if (graph.chart && graph.chart.dispose) {
graph.chart.dispose();
}
} catch (_) {}
return;
}
// Defensive: dispose any existing instance before re-init.
try {
if (graph.chart && graph.chart.dispose) {
graph.chart.dispose();
} else if (typeof echarts !== "undefined" && echarts.getInstanceByDom) {
const existing = echarts.getInstanceByDom(graph.dom);
if (existing) existing.dispose();
}
} catch (_) {}
if (typeof echarts === "undefined" || !echarts.init) {
return;
}
graph.chart = echarts.init(graph.dom, darkModeSwitch.checked ? 'dark' : 'vintage');
graph.chart.setOption(graph.option);
if (graph.onThemeChange) {
graph.onThemeChange();
}
next.push(graph);
});
window.graphList = next;
}
const theme = darkModeSwitch.checked ? 'dark' : 'light';
localStorage.setItem('theme', theme);
applyTheme(theme, darkModeSwitch);
reinitializeGraphs(theme);
});
});
}
export function isDarkMode() {
const currentTheme = localStorage.getItem('theme');
if (currentTheme === null) {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
localStorage.setItem('theme', 'dark');
return true;
} else {
localStorage.setItem('theme', 'light');
return false;
}
}
return currentTheme === 'dark';
return getEffectiveTheme() === 'dark';
}
@@ -292,7 +292,7 @@ function buildDeviceCard(device) {
// Metrics table
const table = document.createElement("table");
table.classList.add("table", "table-sm", "mb-0", "mt-2");
table.classList.add("lqos-table", "lqos-table-tight", "mb-0", "mt-2");
const thead = document.createElement("thead");
const headRow = document.createElement("tr");
File diff suppressed because it is too large. Load diff
@@ -23,31 +23,148 @@ function escapeAttr(text) {
.replaceAll('>', '&gt;');
}
function loadSchedulerStatus() {
const SCHEDULER_STATUS_POLL_MS = 2000;
const SCHEDULER_STATUS_TIMEOUT_MS = 2500;
const SCHEDULER_STATUS_STARTING_GRACE_MS = 30000;
let schedulerStatusPollTimer = null;
let schedulerStatusRequestInFlight = false;
let schedulerStatusFirstRequestedAt = null;
let schedulerStatusHealthy = false;
function listenOnceWithTimeout(eventName, timeoutMs, handler, onTimeout) {
let done = false;
const wrapped = (msg) => {
if (done) return;
done = true;
clearTimeout(timer);
wsClient.off(eventName, wrapped);
handler(msg);
};
const timer = setTimeout(() => {
if (done) return;
done = true;
wsClient.off(eventName, wrapped);
onTimeout();
}, timeoutMs);
wsClient.on(eventName, wrapped);
}
function renderSchedulerStatus(container, state) {
if (!container) return;
let color = "text-secondary";
let icon = "fa-spinner fa-spin";
let label = "Scheduler status is loading";
let buttonText = "Scheduler starting...";
if (state === "healthy") {
color = "text-success";
icon = "fa-check-circle";
label = "Scheduler is available";
buttonText = "Scheduler";
} else if (state === "unavailable") {
color = "text-warning";
icon = "fa-clock";
label = "Scheduler is still unavailable";
buttonText = "Scheduler unavailable";
} else if (state === "error") {
color = "text-danger";
icon = "fa-triangle-exclamation";
label = "Scheduler has an internal error";
buttonText = "Scheduler error";
}
container.innerHTML = `
<button class="nav-link btn btn-link text-start w-100 p-0 border-0 ${color}" type="button" id="schedulerStatusLink" aria-label="${escapeAttr(label)}" title="${escapeAttr(label)}">
<i class="fa fa-fw fa-centerline ${icon}" aria-hidden="true"></i> ${escapeAttr(buttonText)}
</button>`;
$("#schedulerStatus").off("click").on("click", "#schedulerStatusLink", (e) => {
e.preventDefault();
openSchedulerModal();
});
}
function scheduleNextSchedulerStatusPoll(delayMs = SCHEDULER_STATUS_POLL_MS) {
if (schedulerStatusPollTimer) {
clearTimeout(schedulerStatusPollTimer);
}
schedulerStatusPollTimer = setTimeout(() => {
schedulerStatusPollTimer = null;
loadSchedulerStatus();
}, delayMs);
}
function loadSchedulerStatus(force = false) {
const container = document.getElementById('schedulerStatus');
if (!container) return;
listenOnce("SchedulerStatus", (msg) => {
if (schedulerStatusHealthy && !force) {
return;
}
if (schedulerStatusRequestInFlight) {
return;
}
if (schedulerStatusFirstRequestedAt === null || force) {
schedulerStatusFirstRequestedAt = Date.now();
schedulerStatusHealthy = false;
renderSchedulerStatus(container, "loading");
}
schedulerStatusRequestInFlight = true;
listenOnceWithTimeout("SchedulerStatus", SCHEDULER_STATUS_TIMEOUT_MS, (msg) => {
schedulerStatusRequestInFlight = false;
if (!msg || !msg.data) return;
const data = msg.data;
const hasError = !!(data.error && String(data.error).trim().length > 0);
const isHealthy = !!data.available && !hasError;
const color = isHealthy ? 'text-success' : 'text-danger';
const icon = isHealthy ? 'fa-check-circle' : 'fa-triangle-exclamation';
const label = hasError ? 'Scheduler has an internal error' : (data.available ? 'Scheduler is available' : 'Scheduler is unavailable');
container.innerHTML = `
<button class="nav-link btn btn-link text-start w-100 p-0 border-0 ${color}" type="button" id="schedulerStatusLink" aria-label="${escapeAttr(label)}" title="${escapeAttr(label)}">
<i class="fa fa-fw fa-centerline ${icon}" aria-hidden="true"></i> Scheduler
</button>`;
const elapsed = schedulerStatusFirstRequestedAt === null ? 0 : (Date.now() - schedulerStatusFirstRequestedAt);
// Click opens details modal only; no tooltip
$('#schedulerStatus').off('click').on('click', '#schedulerStatusLink', (e) => {
e.preventDefault();
openSchedulerModal();
});
if (hasError) {
schedulerStatusHealthy = false;
renderSchedulerStatus(container, "error");
scheduleNextSchedulerStatusPoll();
return;
}
if (isHealthy) {
schedulerStatusHealthy = true;
renderSchedulerStatus(container, "healthy");
return;
}
schedulerStatusHealthy = false;
if (elapsed < SCHEDULER_STATUS_STARTING_GRACE_MS) {
renderSchedulerStatus(container, "loading");
} else {
renderSchedulerStatus(container, "unavailable");
}
scheduleNextSchedulerStatusPoll();
}, () => {
schedulerStatusRequestInFlight = false;
schedulerStatusHealthy = false;
const elapsed = schedulerStatusFirstRequestedAt === null ? 0 : (Date.now() - schedulerStatusFirstRequestedAt);
if (elapsed < SCHEDULER_STATUS_STARTING_GRACE_MS) {
renderSchedulerStatus(container, "loading");
} else {
renderSchedulerStatus(container, "unavailable");
}
scheduleNextSchedulerStatusPoll();
});
wsClient.send({ SchedulerStatus: {} });
}
function restartSchedulerStatusPolling() {
schedulerStatusHealthy = false;
schedulerStatusRequestInFlight = false;
if (schedulerStatusPollTimer) {
clearTimeout(schedulerStatusPollTimer);
schedulerStatusPollTimer = null;
}
loadSchedulerStatus(true);
}
function openSchedulerModal() {
const modalEl = document.getElementById('schedulerModal');
if (!modalEl) return;
@@ -111,7 +228,7 @@ function renderSearchResults(data) {
}
searchResults.style.visibility = "visible";
let list = document.createElement("table");
list.classList.add("table", "table-striped");
list.classList.add("lqos-table", "lqos-table-compact", "mb-0");
let tbody = document.createElement("tbody");
data.forEach((item) => {
let r = document.createElement("tr");
@@ -132,7 +249,10 @@ function renderSearchResults(data) {
});
clearDiv(searchResults);
list.appendChild(tbody);
searchResults.appendChild(list);
const wrap = document.createElement("div");
wrap.classList.add("table-responsive", "lqos-table-wrap");
wrap.appendChild(list);
searchResults.appendChild(wrap);
}
function doSearch(search) {
@@ -344,7 +464,7 @@ function initUrgentIssues() {
return;
}
const table = document.createElement('table');
table.className = 'table table-sm table-striped';
table.className = 'lqos-table lqos-table-compact mb-0';
const tbody = document.createElement('tbody');
items.forEach((it) => {
const tr = document.createElement('tr');
@@ -367,7 +487,10 @@ function initUrgentIssues() {
});
table.appendChild(tbody);
holder.innerHTML = '';
holder.appendChild(table);
const tableWrap = document.createElement('div');
tableWrap.className = 'table-responsive lqos-table-wrap';
tableWrap.appendChild(table);
holder.appendChild(tableWrap);
$(holder).off('click').on('click', '.urgent-clear', function (e) {
e.preventDefault();
const id = $(this).data('id');
@@ -421,4 +544,10 @@ setupReload();
setupDynamicUrls();
window.lqosInitUrgentIssues = initUrgentIssues;
initSchedulerTooltips();
loadSchedulerStatus();
restartSchedulerStatusPolling();
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
restartSchedulerStatusPolling();
}
});
@@ -29,6 +29,55 @@ const listenOnce = (eventName, handler) => {
wsClient.on(eventName, wrapped);
};
function formatDeviceIp(ip) {
if (typeof ip === "string") {
return ip;
}
if (ip === null || ip === undefined) {
return "-";
}
if (ip instanceof Uint8Array) {
return formatIpBytes(ip);
}
if (Array.isArray(ip)) {
if (ip.every((entry) => Number.isFinite(Number(entry)))) {
return formatIpBytes(ip);
}
return ip.map((entry) => formatDeviceIp(entry)).filter(Boolean).join(", ");
}
if (typeof ip === "object") {
if (ip.V4 !== undefined) {
return formatDeviceIp(ip.V4);
}
if (ip.V6 !== undefined) {
return formatDeviceIp(ip.V6);
}
if (ip.addr !== undefined) {
return formatDeviceIp(ip.addr);
}
if (Array.isArray(ip.data) || ip.data instanceof Uint8Array) {
return formatDeviceIp(ip.data);
}
}
return String(ip);
}
function formatIpBytes(bytes) {
const list = Array.from(bytes);
if (list.length === 4) {
return list.join(".");
}
if (list.length === 16) {
const parts = [];
for (let i = 0; i < list.length; i += 2) {
const part = ((Number(list[i]) || 0) << 8) | (Number(list[i + 1]) || 0);
parts.push(part.toString(16).padStart(4, "0"));
}
return parts.join(":");
}
return list.join(".");
}
function initTooltipsWithin(rootEl) {
if (!rootEl) return;
if (typeof bootstrap === "undefined" || !bootstrap.Tooltip) return;
@@ -86,8 +135,10 @@ function toggleNode(nodeId) {
}
function renderTree() {
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
let treeTable = document.createElement("table");
treeTable.classList.add("table", "table-striped", "table-bordered");
treeTable.classList.add("lqos-table", "lqos-table-tight");
let thead = document.createElement("thead");
thead.appendChild(theading("Name"));
thead.appendChild(theading("Limit"));
@@ -130,7 +181,8 @@ function renderTree() {
// Clear and apply
let target = document.getElementById("tree");
clearDiv(target)
target.appendChild(treeTable);
tableWrap.appendChild(treeTable);
target.appendChild(tableWrap);
initTooltipsWithin(treeTable);
}
@@ -495,55 +547,135 @@ function clientsUpdate(msg) {
let target = document.getElementById("clients");
let table = document.createElement("table");
table.classList.add("table", "table-striped", "table-bordered");
table.classList.add("lqos-table", "lqos-table-tight");
table.appendChild(clientTableHeader());
let tbody = document.createElement("tbody");
clearDiv(target);
const circuits = new Map();
msg.data.forEach((device) => {
if (device.parent_node === myName) {
if (device.parent_node !== myName) {
return;
}
const circuitId = device.circuit_id || `${device.parent_node || ""}:${device.circuit_name || ""}`;
if (!circuits.has(circuitId)) {
circuits.set(circuitId, {
circuit_id: circuitId,
circuit_name: device.circuit_name || "(Unknown circuit)",
parent_node: device.parent_node || "",
plan: {
down: toNumber(device.plan?.down, 0),
up: toNumber(device.plan?.up, 0),
},
device_names: new Set(),
ips: new Set(),
last_seen_nanos: toNumber(device.last_seen_nanos, 0),
bytes_per_second: {down: 0, up: 0},
median_latency: {down: null, up: null},
tcp_packets: {down: 0, up: 0},
tcp_retransmits: {down: 0, up: 0},
});
}
const circuit = circuits.get(circuitId);
if (device.device_name) {
circuit.device_names.add(device.device_name);
}
const ipText = formatDeviceIp(device.ip);
if (ipText && ipText !== "-") {
circuit.ips.add(ipText);
}
circuit.last_seen_nanos = Math.min(circuit.last_seen_nanos, toNumber(device.last_seen_nanos, 0));
circuit.bytes_per_second.down += toNumber(device.bytes_per_second?.down, 0);
circuit.bytes_per_second.up += toNumber(device.bytes_per_second?.up, 0);
circuit.tcp_packets.down += toNumber(device.tcp_packets?.down, 0);
circuit.tcp_packets.up += toNumber(device.tcp_packets?.up, 0);
circuit.tcp_retransmits.down += toNumber(device.tcp_retransmits?.down, 0);
circuit.tcp_retransmits.up += toNumber(device.tcp_retransmits?.up, 0);
const downLatency = toNumber(device.median_latency?.down, 0);
if (downLatency > 0 && (circuit.median_latency.down === null || downLatency > circuit.median_latency.down)) {
circuit.median_latency.down = downLatency;
}
const upLatency = toNumber(device.median_latency?.up, 0);
if (upLatency > 0 && (circuit.median_latency.up === null || upLatency > circuit.median_latency.up)) {
circuit.median_latency.up = upLatency;
}
});
Array.from(circuits.values())
.sort((a, b) => a.circuit_name.localeCompare(b.circuit_name))
.forEach((circuit) => {
let tr = document.createElement("tr");
tr.classList.add("small");
let linkTd = document.createElement("td");
let circuitLink = document.createElement("a");
circuitLink.href = "/circuit.html?id=" + device.circuit_id;
circuitLink.innerText = device.circuit_name;
circuitLink.href = "/circuit.html?id=" + circuit.circuit_id;
circuitLink.innerText = circuit.circuit_name;
circuitLink.classList.add("redactable");
linkTd.appendChild(circuitLink);
tr.appendChild(linkTd);
tr.appendChild(simpleRow(device.device_name, true));
tr.appendChild(simpleRow(device.plan.down + " / " + device.plan.up));
tr.appendChild(simpleRow(device.parent_node, true));
tr.appendChild(simpleRow(device.ip, true));
tr.appendChild(simpleRow(formatLastSeen(device.last_seen_nanos)));
tr.appendChild(simpleRowHtml(formatThroughput(toNumber(device.bytes_per_second.down, 0) * 8, device.plan.down)));
tr.appendChild(simpleRowHtml(formatThroughput(toNumber(device.bytes_per_second.up, 0) * 8, device.plan.up)));
if (device.median_latency !== null) {
tr.appendChild(simpleRowHtml(formatRtt(device.median_latency.down)));
tr.appendChild(simpleRowHtml(formatRtt(device.median_latency.up)));
const deviceNames = Array.from(circuit.device_names);
const deviceCell = simpleRow(
deviceNames.length > 2 ? `${deviceNames[0]}, ${deviceNames[1]} +${deviceNames.length - 2}` : deviceNames.join(", "),
true
);
if (deviceNames.length > 0) {
deviceCell.title = deviceNames.join(", ");
}
tr.appendChild(deviceCell);
tr.appendChild(simpleRow(circuit.plan.down + " / " + circuit.plan.up));
tr.appendChild(simpleRow(circuit.parent_node, true));
const ipList = Array.from(circuit.ips);
const ipCell = simpleRow(
ipList.length > 2 ? `${ipList[0]}, ${ipList[1]} +${ipList.length - 2}` : ipList.join(", "),
true
);
if (ipList.length > 0) {
ipCell.title = ipList.join(", ");
}
tr.appendChild(ipCell);
tr.appendChild(simpleRow(formatLastSeen(circuit.last_seen_nanos)));
tr.appendChild(simpleRowHtml(formatThroughput(circuit.bytes_per_second.down * 8, circuit.plan.down)));
tr.appendChild(simpleRowHtml(formatThroughput(circuit.bytes_per_second.up * 8, circuit.plan.up)));
if (circuit.median_latency.down !== null) {
tr.appendChild(simpleRowHtml(formatRtt(circuit.median_latency.down)));
} else {
tr.appendChild(simpleRow("-"));
}
if (circuit.median_latency.up !== null) {
tr.appendChild(simpleRowHtml(formatRtt(circuit.median_latency.up)));
} else {
tr.appendChild(simpleRow("-"));
}
let retr = 0;
const devicePacketsDown = toNumber(device.tcp_packets.down, 0);
if (devicePacketsDown > 0) {
retr = toNumber(device.tcp_retransmits.down, 0) / devicePacketsDown;
}
tr.appendChild(simpleRowHtml(formatRetransmit(retr)));
retr = 0;
const devicePacketsUp = toNumber(device.tcp_packets.up, 0);
if (devicePacketsUp > 0) {
retr = toNumber(device.tcp_retransmits.up, 0) / devicePacketsUp;
if (circuit.tcp_packets.down > 0) {
retr = circuit.tcp_retransmits.down / circuit.tcp_packets.down;
}
tr.appendChild(simpleRowHtml(formatRetransmit(retr)));
retr = 0;
if (circuit.tcp_packets.up > 0) {
retr = circuit.tcp_retransmits.up / circuit.tcp_packets.up;
}
tr.appendChild(simpleRowHtml(formatRetransmit(retr)));
// Add it
tbody.appendChild(tr);
}
});
});
table.appendChild(tbody);
target.appendChild(table);
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
tableWrap.appendChild(table);
target.appendChild(tableWrap);
}
function onMessage(msg) {
@@ -46,7 +46,7 @@ function loadUnknownIps() {
}
let table = document.createElement("table");
table.classList.add("table", "table-striped");
table.classList.add("lqos-table", "lqos-table-compact");
let thead = document.createElement("thead");
thead.appendChild(theading("IP Address"));
thead.appendChild(theading("Last Seen"));
@@ -64,7 +64,10 @@ function loadUnknownIps() {
});
table.appendChild(tbody);
target.appendChild(table);
const tableWrap = document.createElement("div");
tableWrap.classList.add("lqos-table-wrap");
tableWrap.appendChild(table);
target.appendChild(tableWrap);
});
wsClient.send({ UnknownIps: {} });
}
@@ -0,0 +1,571 @@
<style>
.asn-analysis-page {
display: flex;
flex-direction: column;
gap: 14px;
min-height: calc(100vh - 108px);
}
.asn-analysis-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.asn-analysis-title h2 {
margin: 0;
font-size: 1.55rem;
font-weight: 700;
}
.asn-analysis-title p {
margin: 4px 0 0;
color: var(--bs-secondary-color);
}
.asn-analysis-live {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
padding: 10px 12px;
border: 1px solid var(--lqos-border);
border-radius: 14px;
background: color-mix(in srgb, var(--lqos-surface-solid) 84%, transparent);
box-shadow: var(--lqos-shadow-sm);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.asn-analysis-live-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
background: rgba(16, 185, 129, 0.14);
color: #34d399;
border: 1px solid rgba(52, 211, 153, 0.28);
}
.asn-analysis-row {
display: grid;
grid-template-columns: minmax(420px, 1.32fr) minmax(280px, 0.68fr);
gap: 14px;
}
.asn-analysis-right-stack {
display: grid;
grid-template-rows: auto auto;
gap: 14px;
}
.asn-analysis-grid-bottom {
display: block;
}
.asn-analysis-card {
border: 1px solid var(--lqos-border);
border-radius: var(--lqos-radius-lg);
background: color-mix(in srgb, var(--lqos-surface-solid) 90%, transparent);
box-shadow: var(--lqos-shadow-sm);
overflow: hidden;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.asn-analysis-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
padding: 14px 16px 10px;
border-bottom: 1px solid color-mix(in srgb, var(--lqos-border) 76%, transparent);
}
.asn-analysis-card--selected {
border-color: color-mix(in srgb, var(--bs-primary) 22%, var(--lqos-border) 78%);
box-shadow: var(--lqos-shadow-md);
}
.asn-analysis-card--selected .asn-analysis-card-header {
background:
radial-gradient(500px 180px at 0% 0%, rgba(59, 130, 246, 0.10), transparent 70%),
linear-gradient(180deg, color-mix(in srgb, var(--lqos-surface) 70%, transparent), transparent);
}
.asn-analysis-card-header h3 {
margin: 0;
font-size: 1rem;
font-weight: 700;
}
.asn-analysis-card-header p {
margin: 3px 0 0;
color: var(--bs-secondary-color);
font-size: 0.85rem;
}
.asn-analysis-card-body {
padding: 14px 16px 16px;
}
.asn-analysis-leaderboard-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
}
.asn-analysis-leaderboard-wrap {
max-height: 620px;
overflow: auto;
padding-right: 4px;
}
.asn-analysis-leaderboard-table thead th {
padding: 0 10px 6px;
font-size: 0.78rem;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--bs-secondary-color);
white-space: nowrap;
}
.asn-analysis-leaderboard-row {
cursor: pointer;
}
.asn-analysis-leaderboard-row td {
padding: 12px 10px;
background: color-mix(in srgb, var(--lqos-surface) 58%, transparent);
border-top: 1px solid color-mix(in srgb, var(--lqos-border) 70%, transparent);
border-bottom: 1px solid color-mix(in srgb, var(--lqos-border) 70%, transparent);
vertical-align: middle;
}
.asn-analysis-leaderboard-row td:first-child {
border-left: 1px solid color-mix(in srgb, var(--lqos-border) 70%, transparent);
border-top-left-radius: 14px;
border-bottom-left-radius: 14px;
}
.asn-analysis-leaderboard-row td:last-child {
border-right: 1px solid color-mix(in srgb, var(--lqos-border) 70%, transparent);
border-top-right-radius: 14px;
border-bottom-right-radius: 14px;
}
.asn-analysis-leaderboard-row:hover td {
background: color-mix(in srgb, var(--lqos-hover-bg) 78%, var(--lqos-surface) 44%);
}
.asn-analysis-leaderboard-row.is-selected td {
background: color-mix(in srgb, var(--lqos-active-bg) 78%, var(--lqos-surface) 34%);
border-color: color-mix(in srgb, var(--bs-primary) 28%, var(--lqos-border) 72%);
}
.asn-analysis-leaderboard-rank {
width: 44px;
font-weight: 700;
color: var(--bs-secondary-color);
}
.asn-analysis-asn-name {
display: flex;
flex-direction: column;
gap: 2px;
}
.asn-analysis-asn-name strong {
line-height: 1.2;
}
.asn-analysis-asn-sub {
font-size: 0.82rem;
color: var(--bs-secondary-color);
}
.asn-analysis-qoo-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 700;
border: 1px solid transparent;
}
.asn-analysis-kpis {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
}
.asn-analysis-kpi {
border: 1px solid color-mix(in srgb, var(--bs-primary) 12%, var(--lqos-border) 88%);
border-radius: 16px;
padding: 14px 16px;
background:
linear-gradient(180deg, color-mix(in srgb, var(--lqos-surface-solid) 92%, transparent), color-mix(in srgb, var(--lqos-surface) 64%, transparent));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
.asn-analysis-kpi label {
display: block;
margin-bottom: 4px;
font-size: 0.74rem;
color: var(--bs-secondary-color);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.asn-analysis-kpi strong {
display: block;
font-size: 1.15rem;
line-height: 1.2;
}
.asn-analysis-kpi small {
display: block;
margin-top: 4px;
color: var(--bs-secondary-color);
}
.asn-analysis-rationale {
margin: 12px 0 0;
color: var(--bs-body-color);
font-size: 0.93rem;
padding: 10px 12px;
border-radius: 12px;
background: color-mix(in srgb, var(--lqos-surface) 60%, transparent);
border: 1px solid color-mix(in srgb, var(--lqos-border) 72%, transparent);
}
.asn-analysis-chart {
width: 100%;
min-height: 292px;
height: 292px;
}
.asn-analysis-chart--compact {
min-height: 216px;
height: 216px;
}
.asn-analysis-flow-controls {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.asn-analysis-flow-controls-left,
.asn-analysis-flow-controls-right {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.asn-analysis-flow-context {
margin: 0 0 10px;
font-size: 0.9rem;
color: var(--bs-secondary-color);
}
.asn-analysis-flow-table {
width: 100%;
border-collapse: collapse;
}
.asn-analysis-flow-table th,
.asn-analysis-flow-table td {
padding: 10px 8px;
border-bottom: 1px solid color-mix(in srgb, var(--lqos-border) 72%, transparent);
vertical-align: middle;
}
.asn-analysis-flow-table th {
font-size: 0.76rem;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--bs-secondary-color);
white-space: nowrap;
}
.asn-analysis-flow-table td {
font-size: 0.86rem;
}
.asn-analysis-flow-table canvas {
display: block;
width: 100%;
height: 18px;
}
.asn-analysis-flow-circuit {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 150px;
}
.asn-analysis-flow-circuit strong {
font-size: 0.9rem;
line-height: 1.15;
}
.asn-analysis-flow-circuit span {
font-size: 0.76rem;
color: var(--bs-secondary-color);
}
.asn-analysis-flow-meta {
display: flex;
flex-direction: column;
gap: 2px;
}
.asn-analysis-flow-meta strong {
font-size: 0.84rem;
line-height: 1.15;
}
.asn-analysis-flow-meta span {
font-size: 0.75rem;
color: var(--bs-secondary-color);
}
.asn-analysis-flow-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
padding-top: 12px;
}
.asn-analysis-empty {
padding: 18px 0 4px;
color: var(--bs-secondary-color);
}
.asn-analysis-muted {
color: var(--bs-secondary-color);
}
@media (max-width: 1399.98px) {
.asn-analysis-kpis {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 1199.98px) {
.asn-analysis-row,
.asn-analysis-grid-bottom {
grid-template-columns: 1fr;
}
}
@media (max-width: 767.98px) {
.asn-analysis-kpis {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.asn-analysis-card-header,
.asn-analysis-flow-controls {
flex-direction: column;
align-items: stretch;
}
.asn-analysis-flow-controls-left,
.asn-analysis-flow-controls-right {
width: 100%;
}
}
</style>
<div class="asn-analysis-page">
<section class="asn-analysis-header">
<div class="asn-analysis-title">
<h2>ASN Live Analysis</h2>
<p>Live ASN QoE and flow analysis from recent finished flows and 15-minute executive ASN heatmaps.</p>
</div>
<div class="asn-analysis-live">
<span class="asn-analysis-live-pill"><i class="fa fa-signal"></i> Live</span>
<span class="small text-secondary" id="asnAnalysisCadence">Heatmaps auto-refresh about every second</span>
<span class="small text-secondary" id="asnAnalysisLastRefresh">Waiting for executive ASN heatmaps…</span>
</div>
</section>
<section class="asn-analysis-row">
<div class="asn-analysis-card">
<div class="asn-analysis-card-header">
<div>
<h3>Top 20 ASNs</h3>
<p>Shows the top 20 ASNs by recent traffic and throughput, ordered highest to lowest.</p>
</div>
<div class="d-flex align-items-center gap-2 flex-wrap justify-content-end">
<button class="btn btn-sm btn-outline-secondary" type="button" id="asnAnalysisRefreshButton">
<i class="fa fa-rotate-right me-1"></i>Refresh
</button>
</div>
</div>
<div class="asn-analysis-card-body pt-2">
<div class="table-responsive asn-analysis-leaderboard-wrap">
<table class="asn-analysis-leaderboard-table">
<thead>
<tr>
<th>#</th>
<th>ASN</th>
<th>Traffic</th>
<th>Flows</th>
<th>RTT</th>
<th>Retrans</th>
<th>QoO</th>
</tr>
</thead>
<tbody id="asnAnalysisLeaderboardBody">
<tr>
<td colspan="7" class="asn-analysis-empty">Waiting for executive ASN heatmaps…</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="asn-analysis-right-stack">
<div class="asn-analysis-card">
<div class="asn-analysis-card-header">
<div>
<h3>Latency vs Traffic</h3>
<p>Plots the same top-20 throughput ASNs. Bubble size reflects recent flow count.</p>
</div>
</div>
<div class="asn-analysis-card-body pt-2">
<div id="asnAnalysisBubbleChart" class="asn-analysis-chart"></div>
</div>
</div>
<div class="asn-analysis-card">
<div class="asn-analysis-card-header">
<div>
<h3>Retransmit Outliers</h3>
<p>Same top-20 throughput ASN set, sorted by retransmit so operators can spot outliers quickly.</p>
</div>
</div>
<div class="asn-analysis-card-body pt-2">
<div id="asnAnalysisRetransmitChart" class="asn-analysis-chart asn-analysis-chart--compact"></div>
</div>
</div>
</div>
</section>
<section class="asn-analysis-card asn-analysis-card--selected">
<div class="asn-analysis-card-header">
<div>
<h3 id="asnAnalysisDetailTitle">Selected ASN</h3>
<p id="asnAnalysisDetailSubtitle">Choose an ASN from the leaderboard or bubble chart.</p>
</div>
</div>
<div class="asn-analysis-card-body">
<div class="asn-analysis-kpis" id="asnAnalysisKpis">
<div class="asn-analysis-kpi">
<label>Status</label>
<strong>—</strong>
<small>Waiting for selection</small>
</div>
<div class="asn-analysis-kpi">
<label>Median RTT</label>
<strong>—</strong>
</div>
<div class="asn-analysis-kpi">
<label>Retransmit</label>
<strong>—</strong>
</div>
<div class="asn-analysis-kpi">
<label>Recent Flows</label>
<strong>—</strong>
</div>
<div class="asn-analysis-kpi">
<label>Recent Traffic</label>
<strong>—</strong>
</div>
</div>
<p class="asn-analysis-rationale" id="asnAnalysisRationale">Waiting for enough data to rank ASNs.</p>
</div>
</section>
<section class="asn-analysis-grid-bottom">
<div class="asn-analysis-card">
<div class="asn-analysis-card-header">
<div>
<h3>Flow Evidence</h3>
<p>Recent completed flows for the selected ASN, with more room for the evidence table itself.</p>
</div>
</div>
<div class="asn-analysis-card-body">
<div class="asn-analysis-flow-controls">
<div class="asn-analysis-flow-controls-left">
<button class="btn btn-sm btn-primary" type="button" id="asnAnalysisEvidenceSelectedAsn">
<i class="fa fa-fingerprint me-1"></i>Selected ASN
</button>
<span id="asnAnalysisCountryControl"><i class="fa fa-spin fa-spinner"></i> Loading countries…</span>
<span id="asnAnalysisProtocolControl"><i class="fa fa-spin fa-spinner"></i> Loading protocols…</span>
</div>
<div class="asn-analysis-flow-controls-right">
<label class="small text-secondary" for="asnAnalysisFlowSort">Sort by</label>
<select class="form-select form-select-sm" id="asnAnalysisFlowSort" style="width:auto">
<option value="bytes">Bytes</option>
<option value="duration">Duration</option>
<option value="start">Start Time</option>
</select>
</div>
</div>
<p class="asn-analysis-flow-context" id="asnAnalysisFlowContext">Flow evidence will follow the selected ASN.</p>
<div class="table-responsive">
<table class="asn-analysis-flow-table">
<thead>
<tr>
<th>Circuit</th>
<th>Remote</th>
<th>Protocol</th>
<th>Bytes</th>
<th>RTT</th>
<th style="min-width: 250px;">Timeline</th>
</tr>
</thead>
<tbody id="asnAnalysisFlowTableBody">
<tr>
<td colspan="6" class="asn-analysis-empty">Waiting for selection…</td>
</tr>
</tbody>
</table>
</div>
<div class="asn-analysis-flow-actions">
<div class="small text-secondary" id="asnAnalysisPaginator">Page 1 of 1</div>
<div class="d-flex align-items-center gap-2">
<button class="btn btn-sm btn-outline-secondary" type="button" id="asnAnalysisPrevPage">
<i class="fa fa-arrow-left me-1"></i>Prev
</button>
<button class="btn btn-sm btn-outline-secondary" type="button" id="asnAnalysisNextPage">
Next<i class="fa fa-arrow-right ms-1"></i>
</button>
</div>
</div>
</div>
</div>
</section>
</div>
<script src="asn_analysis.js%CACHEBUSTERS%"></script>
@@ -1,17 +1,22 @@
<div class="row">
<div class="col-1">
<span id="asnList"><i class="fa fa-spin fa-spinner"></i> Loading, Please Wait (this can take a minute)</span>
</div>
<div class="col-1">
<span id="countryList"><i class="fa fa-spin fa-spinner"></i> Loading, Please Wait (this can take a minute)</span>
</div>
<div class="col-1 ms-4">
<span id="protocolList"><i class="fa fa-spin fa-spinner"></i> Loading, Please Wait (this can take a minute)</span>
</div>
</div>
<div class="row">
<div class="col-12" id="asnDetails">
<div class="row justify-content-center">
<div class="col-12 col-xl-8">
<div class="card shadow-sm">
<div class="card-body py-4">
<h2 class="h4 mb-2"><i class="fa fa-globe me-2"></i>ASN Explorer Has Moved</h2>
<p class="text-secondary mb-3">
ASN Explorer is now part of <strong>ASN Analysis</strong>. You are being redirected automatically so the
leaderboard, trend view, and flow evidence stay together on one page.
</p>
<a class="btn btn-primary" href="asn_analysis.html">
<i class="fa fa-arrow-right me-2"></i>Open ASN Analysis
</a>
</div>
</div>
</div>
</div>
<script src="asn_explorer.js%CACHEBUSTERS%"></script>
<script>
window.setTimeout(function () {
window.location.replace("asn_analysis.html");
}, 150);
</script>
@@ -1,55 +1,80 @@
<div class="row">
<div class="col-3">
<h5><i class="fa fa-user"></i> <span id="circuitName" class="redactable"></span></h5>
<table class="table">
<tr>
<td style="font-weight: bold">Parent Node</td>
<td><span id="parentNode" class="redactable"></span></td>
</tr>
<tr>
<td style="font-weight: bold">Maximum Speed</td>
<td><span id="bwMax"></span></td>
</tr>
<tr>
<td style="font-weight: bold">Minimum Speed</td>
<td><span id="bwMin"></span></td>
</tr>
<tr>
<td style="font-weight: bold">RTT Metrics</td>
<td>
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" id="excludeRttToggle">
<label class="form-check-label" for="excludeRttToggle">Exclude from RTT</label>
</div>
<div class="form-text">Exclude this circuit from RTT heatmaps and QoO RTT scoring (QoO will show as unknown).</div>
</td>
</tr>
<tr>
<td colspan="2" id="captureButton">
</td>
</tr>
</table>
</div>
<div class="col-3">
<div class="row g-0">
<div class="col-8">
<div id="bitsGauge" style="width: 100%; height: 200px;"></div>
<div class="row lqos-circuit-top-row align-items-stretch">
<div class="col-12 col-lg-3">
<div class="lqos-circuit-panel">
<h5><i class="fa fa-user"></i> <span id="circuitName" class="redactable lqos-ellipsis-inline"></span></h5>
<div class="lqos-table-wrap">
<table class="lqos-table lqos-table-compact">
<tr>
<td class="table-label-cell">Parent Node</td>
<td class="table-value-cell"><a id="parentNode" class="redactable" href="#"></a></td>
</tr>
<tr>
<td class="table-label-cell">Maximum Speed</td>
<td class="table-value-cell"><span id="bwMax"></span></td>
</tr>
<tr>
<td class="table-label-cell">Minimum Speed</td>
<td class="table-value-cell"><span id="bwMin"></span></td>
</tr>
<tr>
<td class="table-label-cell">RTT Metrics</td>
<td class="table-value-cell">
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" id="excludeRttToggle">
<label class="form-check-label" for="excludeRttToggle">
Exclude
<span
class="ms-1 text-muted"
role="button"
tabindex="0"
aria-label="Exclude from RTT information"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Exclude this circuit from RTT heatmaps and QoO RTT scoring. QoO will show as unknown."
><i class="fas fa-info-circle"></i></span>
</label>
</div>
</td>
</tr>
<tr>
<td colspan="2" id="captureButton">
</td>
</tr>
</table>
</div>
<div class="col-4">
</div>
</div>
<div class="col-12 col-lg-3">
<div class="lqos-circuit-panel lqos-circuit-gauge-panel">
<div class="lqos-circuit-gauge-grid">
<div id="bitsGauge" style="width: 100%; height: 200px;"></div>
<div id="qooGauge" style="width: 100%; height: 200px;"></div>
</div>
</div>
</div>
<div class="col-3" id="throughputGraph">
<i class="fa fa-spinner fa-spin"></i> Loading...
<div class="col-12 col-lg-3">
<div class="lqos-circuit-panel lqos-circuit-chart-panel">
<div id="throughputGraph">
<div class="lqos-loading-block lqos-loading-block-sm">
<i class="fa fa-spinner fa-spin"></i>
<span>Loading throughput…</span>
</div>
</div>
</div>
</div>
<div class="col-3" id="rxmitGraph">
<i class="fa fa-spinner fa-spin"></i> Loading...
<div class="col-12 col-lg-3">
<div class="lqos-circuit-panel lqos-circuit-chart-panel">
<div id="rxmitGraph">
<div class="lqos-loading-block lqos-loading-block-sm">
<i class="fa fa-spinner fa-spin"></i>
<span>Loading retransmits…</span>
</div>
</div>
</div>
</div>
</div>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<ul class="nav nav-tabs lqos-pill-tabs mb-3" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="devs-tab" data-bs-toggle="tab" data-bs-target="#devs" type="button" role="tab" aria-controls="devs" aria-selected="true">
<i class="fa fa-server"></i> Devices
@@ -57,12 +82,12 @@
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="sankey-tab" data-bs-toggle="tab" data-bs-target="#sankey" type="button" role="tab" aria-controls="profile" aria-selected="false">
<i class="fa fa-map"></i> Active Flows <span id="activeFlowCount" class="badge">0</span>
<i class="fa fa-map"></i> Flow Sankey <span id="activeFlowCount" class="badge">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="traffic-tab" data-bs-toggle="tab" data-bs-target="#traffic" type="button" role="tab" aria-controls="contact" aria-selected="false">
<i class="fa fa-table"></i> Network Traffic
<i class="fa fa-table"></i> Traffic Flows <span id="trafficFlowCount" class="badge">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
@@ -72,14 +97,19 @@
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="cake-tab" data-bs-toggle="tab" data-bs-target="#cake" type="button" role="tab" aria-controls="contact" aria-selected="false">
<i class="fa fa-birthday-cake"></i> Shaper Overview
<i class="fa fa-birthday-cake"></i> Queue Stats
</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="devs" role="tabpanel" aria-labelledby="devs-tab">
<div class="row" id="devices">
<i class="fa fa-spinner fa-spin"></i> Loading Devices...
<div class="col-12">
<div class="lqos-loading-block lqos-loading-block-lg">
<i class="fa fa-spinner fa-spin"></i>
<span>Loading devices…</span>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="sankey" role="tabpanel" aria-labelledby="sankey-tab">
@@ -96,14 +126,20 @@
<label for="hideSmallFlows" class="ms-2">Hide Small Flows</label>
</div>
<div class="col-12" id="allTraffic">
<i class="fa fa-spinner fa-spin"></i> Loading Traffic...
<div class="lqos-loading-block lqos-loading-block-lg">
<i class="fa fa-spinner fa-spin"></i>
<span>Loading traffic…</span>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="funnel" role="tabpanel" aria-labelledby="funnel-tab">
<div class="row">
<div class="col-12" id="theFunnel">
<i class="fa fa-spinner fa-spin"></i> Loading Queue Tree...
<div class="lqos-loading-block lqos-loading-block-lg">
<i class="fa fa-spinner fa-spin"></i>
<span>Loading queue tree…</span>
</div>
</div>
</div>
</div>
@@ -129,6 +165,7 @@
</div>
<div class="col-3">
Queue Memory: <span id="cakeQueueMemory">?</span>
<div class="text-muted small mt-1">Queue Type: <span id="cakeQueueType">?</span></div>
</div>
</div>
</div>
@@ -27,8 +27,8 @@
<div class="card-header">Available QoO Profiles</div>
<div class="card-body">
<div id="qooProfilesLoadAlert"></div>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0">
<div class="table-responsive lqos-table-wrap">
<table class="lqos-table lqos-table-compact align-middle mb-0">
<thead>
<tr>
<th>Name</th>
@@ -96,7 +96,7 @@
<div class="tab-pane fade show active" id="v-pills-home" role="tabpanel"
aria-labelledby="v-pills-home-tab">
<h2><i class="fa fa-server"></i> General Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindVersion">Version: </label></td>
<td><input type="text" id="bindVersion" style="background: #ddd"/></td>
@@ -136,7 +136,7 @@
<div class="tab-pane fade" id="v-pills-tuning" role="tabpanel" aria-labelledby="v-pills-tuning-tab">
<h2><i class="fa fa-warning"></i> Tuning Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindStopIrqBalance">Stop IRQ Balancer: </label></td>
<td><input type="checkbox" id="bindStopIrqBalance"/></td>
@@ -187,7 +187,7 @@
<div id="bridgeMode">
<h3>Bridge Mode</h3>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindUseXdpBridge">Use XDP Enhanced Bridge: </label></td>
<td><input type="checkbox" id="bindUseXdpBridge"/></td>
@@ -207,7 +207,7 @@
<div id="OnAStickMode">
<h3>Single-Interface ("On a Stick") Mode</h3>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindSingleInterfaceNic">Network Interface: </label></td>
<td><select id="bindSingleInterfaceNic"></select></td>
@@ -228,7 +228,7 @@
<div class="tab-pane fade" id="v-pills-queues" role="tabpanel" aria-labelledby="v-pills-queues-tab">
<h2><i class="fa car"></i> Queue Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindSqm">SQM Mode: </label></td>
<td>
@@ -283,7 +283,7 @@
<!-- Long-Term Stats Tab -->
<div class="tab-pane fade" id="v-pills-lts" role="tabpanel" aria-labelledby="v-pills-lts-tab">
<h2><i class="fa fa-line-chart"></i> Insight Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindEnableLTS">Send Statistics to Insight: </label></td>
<td><input type="checkbox" id="bindEnableLTS"/></td>
@@ -327,7 +327,7 @@
<!-- Flows Tab -->
<div class="tab-pane fade" id="v-pills-flows" role="tabpanel" aria-labelledby="v-pills-flow-tab">
<h2><i class="fa fa-arrow-circle-down"></i> Flows Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindFlowsTimeout">Flow Timeout (Seconds): </label></td>
<td><input type="number" id="bindFlowsTimeout"/></td>
@@ -359,7 +359,7 @@
<!-- Integration Common Tab -->
<div class="tab-pane fade" id="v-pills-icommon" role="tabpanel" aria-labelledby="v-pills-icommon-tab">
<h2><i class="fa fa-link"></i> Common Integration Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindCircuitNameAsAddress">Use Circuit Name as Address: </label></td>
<td><input type="checkbox" id="bindCircuitNameAsAddress"/></td>
@@ -382,7 +382,7 @@
<!-- Splynx Tab -->
<div class="tab-pane fade" id="v-pills-splynx" role="tabpanel" aria-labelledby="v-pills-splynx-tab">
<h2><i class="fa fa-link"></i> Splynx Integration Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindSplynxEnable">Enable Splynx Integration: </label></td>
<td><input type="checkbox" id="bindSplynxEnable"/></td>
@@ -405,7 +405,7 @@
<!-- Netzur Tab -->
<div class="tab-pane fade" id="v-pills-netzur" role="tabpanel" aria-labelledby="v-pills-netzur-tab">
<h2><i class="fa fa-link"></i> Netzur Integration Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindNetzurEnable">Enable Netzur Integration: </label></td>
<td><input type="checkbox" id="bindNetzurEnable"/></td>
@@ -428,7 +428,7 @@
<!-- UISP Tab -->
<div class="tab-pane fade" id="v-pills-uisp" role="tabpanel" aria-labelledby="v-pills-uisp-tab">
<h2><i class="fa fa-link"></i> UISP Integration Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindUispEnable">Enable UISP Integration: </label></td>
<td><input type="checkbox" id="bindUispEnable"/></td>
@@ -506,7 +506,7 @@
<div class="tab-pane fade" id="v-pills-powercode" role="tabpanel"
aria-labelledby="v-pills-powercode-tab">
<h2><i class="fa fa-link"></i> Powercode Integration Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindPowercodeEnable">Enable Powercode Integration: </label></td>
<td><input type="checkbox" id="bindPowercodeEnable"/></td>
@@ -525,7 +525,7 @@
<!-- Sonar Tab -->
<div class="tab-pane fade" id="v-pills-sonar" role="tabpanel" aria-labelledby="v-pills-sonar-tab">
<h2><i class="fa fa-link"></i> Sonar Integration Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindSonarEnable">Enable Sonar Integration: </label></td>
<td><input type="checkbox" id="bindSonarEnable"/></td>
@@ -560,7 +560,7 @@
<!-- InfluxDB Tab -->
<div class="tab-pane fade" id="v-pills-influxdb" role="tabpanel" aria-labelledby="v-pills-influxdb-tab">
<h2><i class="fa fa-link"></i> InfluxDB Integration Settings</h2>
<table class="table table-bordered">
<table class="lqos-table lqos-table-compact">
<tr>
<td><label for="bindInfluxEnable">Enable InfluxDB Integration: </label></td>
<td><input type="checkbox" id="bindInfluxEnable"/></td>
@@ -4,6 +4,19 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LibreQoS Node Manager</title>
<script>
(function () {
try {
const theme = localStorage.getItem("theme");
document.documentElement.setAttribute(
"data-bs-theme",
theme === "light" ? "light" : "dark"
);
} catch (_) {
document.documentElement.setAttribute("data-bs-theme", "dark");
}
})();
</script>
<link href="vendor/bootstrap.min.css" rel="stylesheet">
<script src="vendor/jquery-3.7.1.min.js"></script>
<script src="vendor/echarts.min.js"></script>
@@ -11,13 +24,13 @@
<script src="vendor/4c979e6ebb.js"></script>
<link href="./node_manager.css" rel="stylesheet">
</head>
<body>
<body class="min-vh-100 d-flex flex-column">
<div id="container" class="pad4">
<div class="row">
<div id="container" class="pad4 flex-fill d-flex align-items-start">
<div class="row w-100">
<div class="col-sm-4"></div>
<div class="col-sm-4">
<div class="card bg-light">
<div class="card shadow-sm border-0">
<div class="card-body">
<h5 class="card-title">First Login</h5>
<p>
@@ -61,10 +74,10 @@
<script src="first-run.js"></script>
<footer class="justify-content-center">
<footer class="justify-content-center text-body-tertiary text-center py-3">
&copy; Copyright 2022-2024, LibreQoE LLC
</footer>
<script src="vendor/bootstrap.bundle.min.js"></script>
</body>
</html>
</html>
@@ -1,6 +1,384 @@
<script type="text/javascript" src="vendor/world.js"></script>
<script type="text/javascript" src="vendor/globe.gl.min.js"></script>
<div class="row" style="height: 100%;">
<div class="col-12" style="height: 100%;" id="flowMap"></div>
<style>
.flow-map-page {
display: flex;
flex-direction: column;
gap: 14px;
min-height: calc(100vh - 108px);
}
.flow-map-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.flow-map-title h2 {
margin: 0;
font-size: 1.55rem;
font-weight: 700;
}
.flow-map-headline {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.flow-map-controls {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
flex-wrap: wrap;
border: 1px solid var(--lqos-border);
background: color-mix(in srgb, var(--lqos-surface-solid) 82%, transparent);
border-radius: 14px;
box-shadow: var(--lqos-shadow-sm);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.flow-map-stage {
position: relative;
flex: 1 1 auto;
height: 70vh;
min-height: 620px;
max-height: 760px;
border-radius: var(--lqos-radius-lg);
border: 1px solid var(--lqos-border);
background: color-mix(in srgb, var(--lqos-surface) 70%, transparent);
box-shadow: var(--lqos-shadow-md);
overflow: hidden;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.flow-map-canvas {
width: 100%;
height: 100%;
min-height: 620px;
}
.flow-map-legend,
.flow-map-legend,
.flow-map-details,
.flow-map-tooltip {
position: absolute;
z-index: 12;
border: 1px solid var(--lqos-border);
background: color-mix(in srgb, var(--lqos-surface-solid) 82%, transparent);
border-radius: 14px;
box-shadow: var(--lqos-shadow-sm);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.flow-map-controls .form-select,
.flow-map-controls .btn {
font-size: 0.9rem;
}
.flow-map-mode-toggle {
display: inline-flex;
align-items: center;
border: 1px solid var(--lqos-border);
border-radius: 999px;
padding: 3px;
background: color-mix(in srgb, var(--lqos-surface-solid) 76%, transparent);
gap: 4px;
}
.flow-map-mode-toggle .btn {
border-radius: 999px;
min-width: 104px;
}
.flow-map-controls .flow-map-divider {
width: 1px;
height: 28px;
background: var(--lqos-border);
}
.flow-map-legend {
right: 16px;
bottom: 16px;
width: min(360px, calc(100% - 32px));
padding: 12px 14px;
}
.flow-map-details {
right: 16px;
top: 16px;
width: min(320px, calc(100% - 32px));
padding: 14px 16px;
display: none;
}
.flow-map-details.active {
display: block;
}
.flow-map-tooltip {
left: 16px;
top: 16px;
width: min(300px, calc(100% - 32px));
padding: 12px 14px;
display: none;
pointer-events: none;
z-index: 14;
}
.flow-map-tooltip.active {
display: block;
}
.flow-map-details-title {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.flow-map-details-title strong {
display: block;
line-height: 1.25;
}
.flow-map-details-body {
display: grid;
gap: 8px;
font-size: 0.9rem;
}
.flow-map-details-row {
display: flex;
justify-content: space-between;
gap: 12px;
}
.flow-map-details-row span:first-child {
color: var(--bs-secondary-color);
}
.flow-map-details-members {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--lqos-border);
font-size: 0.84rem;
line-height: 1.4;
}
.flow-map-legend-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 8px;
}
.flow-map-legend-scale {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.flow-map-legend-gradient {
flex: 1 1 auto;
height: 10px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.12);
}
.flow-map-legend-notes {
font-size: 0.83rem;
line-height: 1.32;
}
.flow-map-legend-notes p {
margin: 0 0 4px 0;
}
.flow-map-badge {
display: inline-flex;
align-items: center;
gap: 6px;
border-radius: 999px;
padding: 0.35rem 0.65rem;
background: color-mix(in srgb, var(--lqos-surface-solid) 80%, transparent);
border: 1px solid var(--lqos-border);
font-size: 0.86rem;
}
.flow-map-cluster-orb {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
transform: translate(-50%, -50%);
width: var(--cluster-size, 34px);
height: var(--cluster-size, 34px);
border-radius: 999px;
border: 2px solid var(--cluster-rim, rgba(255, 255, 255, 0.9));
background:
radial-gradient(circle at 32% 30%, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.88) 14%, color-mix(in srgb, var(--cluster-color, #7cffb2) 70%, white) 24%, var(--cluster-color, #7cffb2) 58%, color-mix(in srgb, var(--cluster-color, #7cffb2) 72%, black) 100%);
box-shadow:
0 0 0 1px color-mix(in srgb, var(--cluster-color, #7cffb2) 58%, rgba(255, 255, 255, 0.16)),
0 0 18px color-mix(in srgb, var(--cluster-color, #7cffb2) 46%, transparent),
0 0 34px color-mix(in srgb, var(--cluster-color, #7cffb2) 28%, transparent);
backdrop-filter: blur(1px);
-webkit-backdrop-filter: blur(1px);
font-weight: 700;
font-size: var(--cluster-font-size, 12px);
line-height: 1;
color: var(--cluster-text, #f8fbff);
text-shadow:
0 1px 2px rgba(3, 7, 18, 0.72),
0 0 10px color-mix(in srgb, var(--cluster-color, #7cffb2) 30%, transparent);
pointer-events: auto;
user-select: none;
cursor: pointer;
}
.flow-map-cluster-orb::before {
content: "";
position: absolute;
inset: -4px;
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--cluster-color, #7cffb2) 42%, transparent);
opacity: 0.82;
}
.flow-map-cluster-orb::after {
content: "";
position: absolute;
inset: 18%;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
opacity: 0.7;
}
@media (max-width: 768px) {
.flow-map-stage,
.flow-map-canvas {
height: auto;
min-height: calc(100vh - 310px);
max-height: none;
}
.flow-map-controls {
padding: 10px;
}
.flow-map-legend {
left: 12px;
right: 12px;
width: auto;
bottom: 12px;
}
.flow-map-details {
left: 12px;
right: 12px;
width: auto;
top: auto;
bottom: 160px;
}
.flow-map-tooltip {
left: 12px;
right: 12px;
top: auto;
width: auto;
bottom: 320px;
}
.flow-map-controls .flow-map-divider {
display: none;
}
}
</style>
<div class="flow-map-page">
<div class="flow-map-header">
<div class="flow-map-title">
<h2><i class="fa fa-globe me-2"></i>Flow Globe</h2>
</div>
<div class="flow-map-headline">
<span class="flow-map-badge"><i class="fa fa-satellite-dish"></i><span id="flowMapSampleCount">0 endpoints</span></span>
<span class="flow-map-badge"><i class="fa fa-clock"></i><span id="flowMapLastUpdated">Waiting for data</span></span>
</div>
</div>
<div class="flow-map-controls">
<button type="button" class="btn btn-sm btn-primary" id="flowMapRefreshBtn">
<i class="fa fa-rotate me-1"></i>Refresh
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="flowMapResetBtn">
<i class="fa fa-rotate-left me-1"></i>Reset View
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="flowMapRotateBtn" aria-pressed="false">
<i class="fa fa-arrows-rotate me-1"></i>Auto Rotate
</button>
<div class="form-check form-switch mb-0 ms-1">
<input class="form-check-input" type="checkbox" role="switch" id="flowMapAutoRefresh">
<label class="form-check-label small text-muted" for="flowMapAutoRefresh">Auto Refresh</label>
</div>
<div class="flow-map-divider"></div>
<label class="small text-muted mb-0">Mode</label>
<div class="flow-map-mode-toggle" role="group" aria-label="Flow Globe mode">
<button type="button" class="btn btn-sm btn-primary" id="flowMapModeLatency" aria-pressed="true">Latency</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="flowMapModeThroughput" aria-pressed="false">Throughput</button>
</div>
<label class="small text-muted mb-0" for="flowMapDensity">Density</label>
<select class="form-select form-select-sm" id="flowMapDensity" style="width: auto;">
<option value="250">Top 250</option>
<option value="1000" selected>Top 1000</option>
<option value="5000">Top 5000</option>
</select>
</div>
<div class="flow-map-stage">
<div id="flowMap" class="flow-map-canvas"></div>
<div class="flow-map-tooltip" id="flowMapTooltip"></div>
<div class="flow-map-details" id="flowMapDetails">
<div class="flow-map-details-title">
<div>
<strong id="flowMapDetailsTitle">Selected endpoint</strong>
<span class="small text-muted" id="flowMapDetailsSubtitle">Click a marker to pin details.</span>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary" id="flowMapDetailsClose">
<i class="fa fa-xmark"></i>
</button>
</div>
<div class="flow-map-details-body" id="flowMapDetailsBody"></div>
<div class="flow-map-details-members text-muted" id="flowMapDetailsMembers" style="display: none;"></div>
</div>
<div class="flow-map-legend">
<div class="flow-map-legend-title">
<strong><i class="fa fa-swatchbook me-1"></i><span id="flowMapLegendTitle">Latency Gradient</span></strong>
<span class="small text-muted" id="flowMapLegendStatus">Sizing by traffic volume</span>
</div>
<div class="flow-map-legend-scale">
<span class="small text-muted" id="flowMapLegendMin">Low</span>
<div class="flow-map-legend-gradient" id="flowMapLegendGradient"></div>
<span class="small text-muted" id="flowMapLegendMax">High</span>
</div>
<div class="flow-map-legend-notes text-muted">
<p id="flowMapLegendBody">Marker color shows RTT and marker size shows recent traffic volume.</p>
<p class="mb-0">Drag to rotate. Use the mouse wheel or trackpad pinch to zoom.</p>
</div>
</div>
</div>
</div>
<script src="flow_map.js%CACHEBUSTERS%"></script>
@@ -4,6 +4,19 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LibreQoS Node Manager</title>
<script>
(function () {
try {
const theme = localStorage.getItem("theme");
document.documentElement.setAttribute(
"data-bs-theme",
theme === "light" ? "light" : "dark"
);
} catch (_) {
document.documentElement.setAttribute("data-bs-theme", "dark");
}
})();
</script>
<link href="vendor/bootstrap.min.css" rel="stylesheet">
<script src="vendor/jquery-3.7.1.min.js"></script>
<script src="vendor/echarts.min.js"></script>
@@ -11,13 +24,13 @@
<script src="vendor/4c979e6ebb.js"></script>
<link href="./node_manager.css" rel="stylesheet">
</head>
<body>
<body class="min-vh-100 d-flex flex-column">
<div id="container" class="pad4">
<div class="row">
<div id="container" class="pad4 flex-fill d-flex align-items-start">
<div class="row w-100">
<div class="col-sm-4"></div>
<div class="col-sm-4">
<div class="card bg-light">
<div class="card shadow-sm border-0">
<div class="card-body">
<h5 class="card-title">Login</h5>
<p>Please enter a username and password to access LibreQoS.</p>
@@ -40,14 +53,14 @@
</div>
</div>
<div id="loginError" class="alert alert-danger d-none mt-3">
<div id="loginError" class="alert alert-danger d-none mt-3 mx-auto" style="max-width: 720px;">
<i class="fas fa-exclamation-triangle me-2"></i>
Login failed. You can manage users via the <code>lqusers</code> CLI tool on the LibreQoS server.
</div>
<script src="login.js"></script>
<footer class="justify-content-center">
<footer class="justify-content-center text-body-tertiary text-center py-3">
&copy; Copyright 2022-2024, LibreQoE LLC
</footer>
@@ -57,10 +57,22 @@ body {
margin: 0;
font-family: var(--lqos-font-sans);
font-size: 1rem;
background: var(--lqos-app-bg);
background-color: transparent;
color: var(--bs-body-color);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: relative;
}
body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background: var(--lqos-app-bg);
background-repeat: no-repeat;
background-size: cover;
background-position: center top;
}
#wrapper {
display: flex;
@@ -79,10 +91,10 @@ body {
padding: 12px;
}
}
.lqos_logo {
html[data-bs-theme="light"] .lqos_logo {
filter: invert(100%);
}
.dark-mode .lqos_logo {
html[data-bs-theme="dark"] .lqos_logo {
filter: none;
}
.sidebar {
@@ -246,6 +258,29 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
color: var(--bs-secondary-color);
text-decoration: none;
}
.dashbox-body-scroll {
overflow-y: auto;
}
.dashbox-body-scroll-top10 {
min-height: 25rem;
max-height: 25rem;
}
@media (max-width: 1399.98px) {
.dashbox-body-scroll-top10 {
min-height: 22rem;
max-height: 22rem;
}
}
@media (max-width: 991.98px) {
.dashbox-body-scroll-top10 {
min-height: 18rem;
max-height: 18rem;
}
}
.dashbox .dashbox-title a:hover {
color: var(--bs-body-color);
}
@@ -267,6 +302,7 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
.btn:hover {
transform: translateY(-1px);
}
.lqos-pill-tabs,
#dashboard .nav-tabs {
border-bottom: 0;
gap: 0.5rem;
@@ -274,13 +310,16 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
overflow-x: auto;
padding-bottom: 0.25rem;
}
.lqos-pill-tabs::-webkit-scrollbar,
#dashboard .nav-tabs::-webkit-scrollbar {
height: 6px;
}
.lqos-pill-tabs::-webkit-scrollbar-thumb,
#dashboard .nav-tabs::-webkit-scrollbar-thumb {
background: var(--lqos-border);
border-radius: 999px;
}
.lqos-pill-tabs .nav-link,
#dashboard .nav-tabs .nav-link {
border: 1px solid transparent;
border-radius: 999px;
@@ -288,10 +327,12 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
color: var(--bs-secondary-color);
background: transparent;
}
.lqos-pill-tabs .nav-link:hover,
#dashboard .nav-tabs .nav-link:hover {
background: var(--lqos-hover-bg);
color: var(--bs-body-color);
}
.lqos-pill-tabs .nav-link.active,
#dashboard .nav-tabs .nav-link.active {
background: var(--lqos-surface-solid);
border-color: var(--lqos-border);
@@ -306,8 +347,86 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
width: 100%;
height: 250px;
}
.lqos-loading-block {
min-height: 220px;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1rem;
border: 1px solid var(--lqos-border);
border-radius: var(--lqos-radius-md);
background: linear-gradient(
180deg,
color-mix(in srgb, var(--lqos-surface-2) 88%, transparent) 0%,
var(--lqos-surface-solid) 100%
);
color: var(--bs-secondary-color);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.lqos-loading-block.lqos-loading-block-sm {
min-height: 140px;
}
.lqos-loading-block.lqos-loading-block-lg {
min-height: 320px;
}
.lqos-loading-block i {
font-size: 1rem;
}
.lqos-circuit-top-row {
--bs-gutter-x: 1rem;
--bs-gutter-y: 1rem;
}
.lqos-circuit-panel {
height: 100%;
min-height: 230px;
border: 1px solid var(--lqos-border);
border-radius: var(--lqos-radius-md);
background: linear-gradient(
180deg,
color-mix(in srgb, var(--lqos-surface-2) 88%, transparent) 0%,
var(--lqos-surface-solid) 100%
);
box-shadow: var(--lqos-shadow-sm);
padding: 0.85rem;
}
.lqos-circuit-panel h5 {
display: flex;
align-items: center;
gap: 0.45rem;
min-width: 0;
}
.lqos-circuit-panel h5 .fa {
flex: 0 0 auto;
}
.lqos-circuit-panel .lqos-table-wrap {
height: calc(100% - 2rem);
}
.lqos-circuit-panel .lqos-table {
height: 100%;
}
.lqos-circuit-gauge-panel {
display: flex;
align-items: center;
justify-content: center;
}
.lqos-circuit-gauge-grid {
width: 100%;
display: grid;
grid-template-columns: 2fr 1fr;
align-items: center;
gap: 0.25rem;
}
.lqos-circuit-chart-panel {
display: flex;
align-items: center;
}
.lqos-circuit-chart-panel > div {
width: 100%;
height: 200px;
}
.tiny {
font-size: 6pt;
font-size: 0.72rem;
}
.darkenBackground {
z-index: 200;
@@ -351,6 +470,155 @@ h5 {
color: var(--bs-secondary-color);
}
.lqos-table-wrap {
width: 100%;
overflow: auto;
border: 1px solid var(--lqos-border);
border-radius: var(--lqos-radius-md);
background: linear-gradient(
180deg,
color-mix(in srgb, var(--lqos-surface-2) 88%, transparent) 0%,
var(--lqos-surface-solid) 100%
);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.lqos-table,
.dash-table {
width: 100%;
margin: 0;
border-collapse: separate;
border-spacing: 0;
color: var(--bs-body-color);
}
.lqos-table thead th,
.dash-table thead th {
padding: 0.75rem 0.85rem;
border-bottom: 1px solid var(--lqos-border);
background: color-mix(in srgb, var(--lqos-surface-2) 92%, transparent);
color: var(--bs-secondary-color);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
white-space: nowrap;
}
.lqos-table tbody td,
.dash-table tbody td {
padding: 0.72rem 0.85rem;
border-top: 1px solid color-mix(in srgb, var(--lqos-border) 72%, transparent);
color: var(--bs-body-color);
vertical-align: middle;
}
.lqos-table tbody tr:first-child td,
.dash-table tbody tr:first-child td {
border-top: 0;
}
.lqos-table tbody tr:nth-child(2n) td,
.dash-table tbody tr:nth-child(2n) td {
background: color-mix(in srgb, var(--lqos-surface-2) 56%, transparent);
}
.lqos-table tbody tr:hover td,
.dash-table tbody tr:hover td {
background: color-mix(in srgb, var(--lqos-hover-bg) 88%, transparent);
}
.lqos-table a,
.dash-table a {
color: var(--bs-body-color);
text-decoration-line: underline;
text-decoration-thickness: 0.08em;
text-underline-offset: 0.14em;
text-decoration-color: color-mix(in srgb, currentColor 58%, transparent);
}
.lqos-table a:hover,
.dash-table a:hover {
color: var(--bs-info);
text-decoration-color: currentColor;
}
.lqos-table.lqos-table-compact thead th,
.dash-table.lqos-table-compact thead th {
padding: 0.55rem 0.7rem;
}
.lqos-table.lqos-table-compact tbody td,
.dash-table.lqos-table-compact tbody td {
padding: 0.55rem 0.7rem;
}
.lqos-table.lqos-table-tight thead th,
.dash-table.lqos-table-tight thead th {
padding: 0.45rem 0.6rem;
}
.lqos-table.lqos-table-tight tbody td,
.dash-table.lqos-table-tight tbody td {
padding: 0.45rem 0.6rem;
}
.lqos-table.lqos-table-tight {
font-size: 0.9rem;
}
.lqos-table .table-label-cell,
.dash-table .table-label-cell {
width: 40%;
color: var(--bs-secondary-color);
font-weight: 600;
}
.lqos-table .table-value-cell,
.dash-table .table-value-cell {
color: var(--bs-body-color);
}
.lqos-table .muted-meta,
.dash-table .muted-meta {
color: var(--bs-tertiary-color);
}
.lqos-rtt-metric-cell {
min-width: 9.5rem;
}
.lqos-rtt-metric {
min-height: 2.4rem;
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.18rem;
}
.lqos-rtt-metric-line {
display: flex;
align-items: baseline;
gap: 0.35rem;
white-space: nowrap;
}
.lqos-rtt-metric-label {
color: var(--bs-secondary-color);
font-size: 0.72rem;
font-weight: 600;
}
.lqos-rtt-metric-value {
font-size: 0.72rem;
font-variant-numeric: tabular-nums;
}
.lqos-rtt-metric-line.text-secondary .lqos-rtt-metric-value {
color: inherit;
}
.lqos-ellipsis-inline {
display: inline-block;
min-width: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
}
.lqos-table-cell-ellipsis {
display: block;
max-width: 12rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.lqos-asn-cell {
width: 11rem;
max-width: 11rem;
}
.lqos-asn-cell .lqos-table-cell-ellipsis {
max-width: 100%;
}
/* Funky tricks for the tree view */
.overlayThroughputWrapper {
position: relative;
@@ -398,9 +666,6 @@ h5 {
box-shadow: var(--lqos-shadow-md);
background: var(--lqos-surface-solid);
}
.dash-table {
width: 100%;
}
.fa-centerline {
vertical-align: middle;
}
@@ -416,6 +681,18 @@ table tr td {
table tr td a {
color: var(--bs-secondary-color);
}
.lqos-table>:not(caption)>*>*,
.dash-table>:not(caption)>*>* {
color: inherit;
}
.lqos-table thead,
.dash-table thead,
.lqos-table tr td,
.dash-table tr td,
.lqos-table tr td a,
.dash-table tr td a {
color: inherit;
}
/* For the config panel */
@@ -0,0 +1,362 @@
<script type="text/javascript" src="vendor/maplibre-gl.js"></script>
<style>
@import url("vendor/maplibre-gl.css");
.site-map-page {
display: flex;
flex-direction: column;
gap: 14px;
min-height: calc(100vh - 108px);
}
.site-map-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.site-map-title h2 {
margin: 0;
font-size: 1.55rem;
font-weight: 700;
}
.site-map-headline {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.site-map-controls {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
flex-wrap: wrap;
border: 1px solid var(--lqos-border);
background: color-mix(in srgb, var(--lqos-surface-solid) 82%, transparent);
border-radius: 14px;
box-shadow: var(--lqos-shadow-sm);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.site-map-stage {
position: relative;
flex: 1 1 auto;
height: 70vh;
min-height: 620px;
max-height: 780px;
border-radius: var(--lqos-radius-lg);
border: 1px solid var(--lqos-border);
background: color-mix(in srgb, var(--lqos-surface) 70%, transparent);
box-shadow: var(--lqos-shadow-md);
overflow: hidden;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.site-map-canvas {
width: 100%;
height: 100%;
min-height: 620px;
}
.site-map-legend,
.site-map-details,
.site-map-unmapped,
.site-map-tooltip {
position: absolute;
z-index: 12;
border: 1px solid var(--lqos-border);
background: color-mix(in srgb, var(--lqos-surface-solid) 88%, transparent);
border-radius: 14px;
box-shadow: var(--lqos-shadow-sm);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.site-map-mode-toggle {
display: inline-flex;
align-items: center;
border: 1px solid var(--lqos-border);
border-radius: 999px;
padding: 3px;
background: color-mix(in srgb, var(--lqos-surface-solid) 76%, transparent);
gap: 4px;
}
.site-map-mode-toggle .btn {
border-radius: 999px;
min-width: 90px;
}
.site-map-controls .site-map-divider {
width: 1px;
height: 28px;
background: var(--lqos-border);
}
.site-map-legend {
right: 16px;
bottom: 16px;
width: min(340px, calc(100% - 32px));
padding: 12px 14px;
}
.site-map-details {
right: 16px;
top: 16px;
width: min(330px, calc(100% - 32px));
padding: 14px 16px;
display: none;
}
.site-map-unmapped {
left: 16px;
top: 16px;
width: min(340px, calc(100% - 32px));
padding: 12px 14px;
display: none;
}
.site-map-unmapped.is-open {
display: block;
}
.site-map-tooltip {
display: none;
pointer-events: none;
min-width: 220px;
max-width: 320px;
padding: 10px 12px;
z-index: 20;
}
.site-map-tooltip.is-visible {
display: block;
}
.site-map-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
border-radius: 999px;
background: color-mix(in srgb, var(--lqos-surface-solid) 78%, transparent);
border: 1px solid var(--lqos-border);
font-size: 0.82rem;
font-weight: 600;
}
.site-map-gradient {
height: 10px;
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--lqos-border) 78%, transparent);
margin: 6px 0 10px;
}
.site-map-legend-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
font-size: 0.85rem;
}
.site-map-size-row {
display: flex;
align-items: flex-end;
gap: 10px;
margin-top: 8px;
}
.site-map-size-dot {
border-radius: 999px;
border: 1px solid rgba(255,255,255,0.45);
background: radial-gradient(circle at 35% 35%, rgba(255,255,255,0.9) 0%, rgba(100, 160, 255, 0.95) 42%, rgba(60, 108, 196, 0.92) 100%);
box-shadow: 0 0 18px rgba(82, 155, 255, 0.35);
flex: 0 0 auto;
}
.site-map-panel-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
}
.site-map-metric-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px 10px;
font-size: 0.85rem;
}
.site-map-metric {
border: 1px solid color-mix(in srgb, var(--lqos-border) 78%, transparent);
border-radius: 12px;
padding: 8px 10px;
background: color-mix(in srgb, var(--lqos-surface) 62%, transparent);
}
.site-map-metric label {
display: block;
font-size: 0.73rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--bs-secondary-color);
margin-bottom: 2px;
}
.site-map-list {
display: flex;
flex-direction: column;
gap: 8px;
max-height: 42vh;
overflow: auto;
}
.site-map-list-group h6 {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--bs-secondary-color);
margin-bottom: 6px;
}
.site-map-list-item {
border: 1px solid color-mix(in srgb, var(--lqos-border) 72%, transparent);
border-radius: 12px;
padding: 8px 10px;
background: color-mix(in srgb, var(--lqos-surface) 62%, transparent);
font-size: 0.86rem;
}
.site-map-empty {
color: var(--bs-secondary-color);
font-size: 0.88rem;
}
.site-map-popup {
min-width: 220px;
}
.maplibregl-popup-content {
border-radius: 14px;
border: 1px solid var(--lqos-border);
box-shadow: var(--lqos-shadow-md);
background: color-mix(in srgb, var(--lqos-surface-solid) 96%, transparent);
color: var(--bs-body-color);
}
.maplibregl-popup-close-button {
color: var(--bs-body-color);
}
@media (max-width: 991.98px) {
.site-map-page {
min-height: auto;
}
.site-map-stage {
height: 64vh;
min-height: 520px;
max-height: none;
}
}
@media (max-width: 767.98px) {
.site-map-stage {
height: 60vh;
min-height: 460px;
}
.site-map-legend,
.site-map-details,
.site-map-unmapped {
position: static;
width: 100%;
margin-top: 10px;
}
.site-map-unmapped.is-open,
.site-map-details {
display: block;
}
}
</style>
<div class="site-map-page">
<div class="site-map-header">
<div class="site-map-title">
<div class="site-map-headline">
<h2><i class="fa fa-map-location-dot me-2"></i>Site Map</h2>
</div>
</div>
<div class="site-map-controls">
<div class="site-map-mode-toggle" role="group" aria-label="Site Map mode">
<button type="button" class="btn btn-sm btn-primary active" id="siteMapModeQoo">QoO</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="siteMapModeRtt">RTT</button>
</div>
<span class="site-map-divider" aria-hidden="true"></span>
<span class="site-map-chip" id="siteMapStatusChip"><i class="fa fa-spinner fa-spin"></i> Waiting for data</span>
<span class="site-map-chip" id="siteMapUpdatedChip"><i class="fa fa-clock"></i> Not updated yet</span>
<button type="button" class="btn btn-sm btn-outline-secondary" id="siteMapUnmappedToggle">
<i class="fa fa-location-crosshairs me-1"></i> Unmapped <span class="badge text-bg-secondary ms-1" id="siteMapUnmappedBadge">0</span>
</button>
</div>
</div>
<div class="site-map-stage" id="siteMapStage">
<div id="siteMapCanvas" class="site-map-canvas"></div>
<div class="site-map-unmapped" id="siteMapUnmappedPanel">
<div class="site-map-panel-title">
<strong>Unmapped Nodes</strong>
<button type="button" class="btn btn-sm btn-outline-secondary" id="siteMapUnmappedClose">
<i class="fa fa-times"></i>
</button>
</div>
<div class="site-map-empty mb-2" id="siteMapUnmappedSummary">All mapped nodes have display coordinates.</div>
<div class="site-map-list" id="siteMapUnmappedList"></div>
</div>
<div class="site-map-details" id="siteMapDetails">
<div class="site-map-panel-title">
<strong id="siteMapDetailsTitle">Select a node</strong>
<button type="button" class="btn btn-sm btn-outline-secondary" id="siteMapDetailsClose">
<i class="fa fa-times"></i>
</button>
</div>
<div class="text-muted small mb-2" id="siteMapDetailsSubtitle">Click a site or AP marker to inspect it.</div>
<div class="site-map-metric-grid" id="siteMapDetailsGrid"></div>
</div>
<div class="site-map-legend">
<div class="fw-semibold">Metric Gradient</div>
<div class="site-map-gradient" id="siteMapLegendGradient"></div>
<div class="site-map-legend-row">
<span id="siteMapLegendLow">Poor</span>
<span id="siteMapLegendHigh">Healthy</span>
</div>
<div class="fw-semibold mt-3">Marker Size</div>
<div class="site-map-size-row">
<span class="site-map-size-dot" style="width: 12px; height: 12px;"></span>
<span class="site-map-size-dot" style="width: 20px; height: 20px;"></span>
<span class="site-map-size-dot" style="width: 30px; height: 30px;"></span>
<div class="text-muted small">Combined recent throughput over the last 30 seconds.</div>
</div>
<div class="text-muted small mt-3">
Zoomed out views emphasize sites. As you zoom in, APs take over where coordinate coverage allows.
</div>
</div>
</div>
</div>
<script src="site_map.js%CACHEBUSTERS%"></script>
@@ -18,9 +18,9 @@
<h6 class="mb-0 text-secondary"><i class="fa fa-tachometer-alt me-2 text-primary"></i>Current limits</h6>
<small class="text-muted">StormguardStatus</small>
</div>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0">
<thead class="table-light">
<div class="table-responsive lqos-table-wrap">
<table class="lqos-table lqos-table-compact align-middle mb-0">
<thead>
<tr>
<th scope="col"><i class="fa fa-sitemap me-1"></i>Site</th>
<th scope="col"><i class="fa fa-arrow-down me-1 text-primary"></i>Download (Mbps)</th>
@@ -43,9 +43,9 @@
<h6 class="mb-0 text-secondary"><i class="fa fa-microscope me-2 text-warning"></i>Evaluation data</h6>
<small class="text-muted">StormguardDebug</small>
</div>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0">
<thead class="table-light">
<div class="table-responsive lqos-table-wrap">
<table class="lqos-table lqos-table-compact align-middle mb-0">
<thead>
<tr>
<th scope="col">Site</th>
<th scope="col">Dir</th>
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>%%TITLE%%</title>
<link rel="icon" href="favicon.png">
<script>
(function () {
try {
const theme = localStorage.getItem("theme");
document.documentElement.setAttribute(
"data-bs-theme",
theme === "light" ? "light" : "dark"
);
} catch (_) {
document.documentElement.setAttribute("data-bs-theme", "dark");
}
})();
</script>
<link href="vendor/bootstrap.min.css" rel="stylesheet">
<script src="vendor/jquery-3.7.1.min.js"></script>
<script src="vendor/echarts.min.js"></script>
@@ -71,10 +84,16 @@
<i class="fa fa-fw fa-centerline fa-address-card nav-icon"></i> Unknown IP <span id="unknownIpCount" class="badge menu-badge text-warning muted">?</span>
</a>
</li>
<!-- Flow Map -->
<!-- Site Map -->
<li class="nav-item">
<a class="nav-link" href="site_map.html">
<i class="fa fa-fw fa-centerline fa-map-location-dot nav-icon"></i> Site Map
</a>
</li>
<!-- Flow Globe -->
<li class="nav-item">
<a class="nav-link" href="flow_map.html">
<i class="fa fa-fw fa-centerline fa-map nav-icon"></i> Flow Map
<i class="fa fa-fw fa-centerline fa-globe nav-icon"></i> Flow Globe
</a>
</li>
<!-- Tree Sankey -->
@@ -83,10 +102,10 @@
<i class="fa fa-fw fa-centerline fa-server nav-icon"></i> Tree Overview
</a>
</li>
<!-- ASN Explorer -->
<!-- ASN Analysis -->
<li class="nav-item">
<a class="nav-link" href="asn_explorer.html">
<i class="fa fa-fw fa-centerline fa-globe nav-icon"></i> ASN Explorer
<a class="nav-link" href="asn_analysis.html">
<i class="fa fa-fw fa-centerline fa-globe nav-icon"></i> ASN Analysis
</a>
</li>
<!-- CPU Affinity -->
@@ -1,40 +1,42 @@
<div class="row">
<div class="col-4">
<h5><i class="fa fa-tree"></i> Network Tree (<span id="nodeName" class="redactable"></span>)</h5>
<table class="table table-sm">
<thead>
<th></th>
<th>Download</th>
<th>Upload</th>
</thead>
<tbody>
<tr class="small">
<td style="font-weight: bold;">Limits:</td>
<td class="small"><span id="parentLimitsD"></span></td>
<td class="small"><span id="parentLimitsU"></span></td>
</tr>
<tr class="small">
<td style="font-weight: bold;">Throughput:</td>
<td><span id="parentTpD"></span></td>
<td><span id="parentTpU"></span></td>
</tr>
<tr class="small">
<td style="font-weight: bold;">RTT:</td>
<td><span id="parentRttD"></span></td>
<td><span id="parentRttU"></span></td>
</tr>
<tr class="small">
<td style="font-weight: bold;">QoO:</td>
<td><span id="parentQooD"></span></td>
<td><span id="parentQooU"></span></td>
</tr>
<tr class="small">
<td style="font-weight: bold;">TCP Retransmits:</td>
<td><span id="parentRxmitD"></span></td>
<td><span id="parentRxmitU"></span></td>
</tr>
</tbody>
</table>
<div class="lqos-table-wrap">
<table class="lqos-table lqos-table-compact">
<thead>
<th></th>
<th>Download</th>
<th>Upload</th>
</thead>
<tbody>
<tr class="small">
<td class="table-label-cell">Limits</td>
<td class="small"><span id="parentLimitsD"></span></td>
<td class="small"><span id="parentLimitsU"></span></td>
</tr>
<tr class="small">
<td class="table-label-cell">Throughput</td>
<td><span id="parentTpD"></span></td>
<td><span id="parentTpU"></span></td>
</tr>
<tr class="small">
<td class="table-label-cell">RTT</td>
<td><span id="parentRttD"></span></td>
<td><span id="parentRttU"></span></td>
</tr>
<tr class="small">
<td class="table-label-cell">QoO</td>
<td><span id="parentQooD"></span></td>
<td><span id="parentQooU"></span></td>
</tr>
<tr class="small">
<td class="table-label-cell">TCP Retransmits</td>
<td><span id="parentRxmitD"></span></td>
<td><span id="parentRxmitU"></span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="row">
@@ -0,0 +1,178 @@
{"type":"FeatureCollection","features":[ {"type":"Feature","properties":{"name":"Afghanistan"},"geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[62.230651,35.270664],[62.984662,35.404041],[63.193538,35.857166],[63.982896,36.007957],[64.546479,36.312073],[64.746105,37.111818],[65.588948,37.305217],[65.745631,37.661164],[66.217385,37.39379],[66.518607,37.362784],[67.075782,37.356144],[67.83,37.144994],[68.135562,37.023115],[68.859446,37.344336],[69.196273,37.151144],[69.518785,37.608997],[70.116578,37.588223],[70.270574,37.735165],[70.376304,38.138396],[70.806821,38.486282],[71.348131,38.258905],[71.239404,37.953265],[71.541918,37.905774],[71.448693,37.065645],[71.844638,36.738171],[72.193041,36.948288],[72.63689,37.047558],[73.260056,37.495257],[73.948696,37.421566],[74.980002,37.41999],[75.158028,37.133031],[74.575893,37.020841],[74.067552,36.836176],[72.920025,36.720007],[71.846292,36.509942],[71.262348,36.074388],[71.498768,35.650563],[71.613076,35.153203],[71.115019,34.733126],[71.156773,34.348911],[70.881803,33.988856],[69.930543,34.02012],[70.323594,33.358533],[69.687147,33.105499],[69.262522,32.501944],[69.317764,31.901412],[68.926677,31.620189],[68.556932,31.71331],[67.792689,31.58293],[67.683394,31.303154],[66.938891,31.304911],[66.381458,30.738899],[66.346473,29.887943],[65.046862,29.472181],[64.350419,29.560031],[64.148002,29.340819],[63.550261,29.468331],[62.549857,29.318572],[60.874248,29.829239],[61.781222,30.73585],[61.699314,31.379506],[60.941945,31.548075],[60.863655,32.18292],[60.536078,32.981269],[60.9637,33.528832],[60.52843,33.676446],[60.803193,34.404102],[61.210817,35.650072]]]},"id":"AFG"},
{"type":"Feature","properties":{"name":"Angola"},"geometry":{"type":"MultiPolygon","coordinates":[[[[16.326528,-5.87747],[16.57318,-6.622645],[16.860191,-7.222298],[17.089996,-7.545689],[17.47297,-8.068551],[18.134222,-7.987678],[18.464176,-7.847014],[19.016752,-7.988246],[19.166613,-7.738184],[19.417502,-7.155429],[20.037723,-7.116361],[20.091622,-6.94309],[20.601823,-6.939318],[20.514748,-7.299606],[21.728111,-7.290872],[21.746456,-7.920085],[21.949131,-8.305901],[21.801801,-8.908707],[21.875182,-9.523708],[22.208753,-9.894796],[22.155268,-11.084801],[22.402798,-10.993075],[22.837345,-11.017622],[23.456791,-10.867863],[23.912215,-10.926826],[24.017894,-11.237298],[23.904154,-11.722282],[24.079905,-12.191297],[23.930922,-12.565848],[24.016137,-12.911046],[21.933886,-12.898437],[21.887843,-16.08031],[22.562478,-16.898451],[23.215048,-17.523116],[21.377176,-17.930636],[18.956187,-17.789095],[18.263309,-17.309951],[14.209707,-17.353101],[14.058501,-17.423381],[13.462362,-16.971212],[12.814081,-16.941343],[12.215461,-17.111668],[11.734199,-17.301889],[11.640096,-16.673142],[11.778537,-15.793816],[12.123581,-14.878316],[12.175619,-14.449144],[12.500095,-13.5477],[12.738479,-13.137906],[13.312914,-12.48363],[13.633721,-12.038645],[13.738728,-11.297863],[13.686379,-10.731076],[13.387328,-10.373578],[13.120988,-9.766897],[12.87537,-9.166934],[12.929061,-8.959091],[13.236433,-8.562629],[12.93304,-7.596539],[12.728298,-6.927122],[12.227347,-6.294448],[12.322432,-6.100092],[12.735171,-5.965682],[13.024869,-5.984389],[13.375597,-5.864241],[16.326528,-5.87747]]],[[[12.436688,-5.684304],[12.182337,-5.789931],[11.914963,-5.037987],[12.318608,-4.60623],[12.62076,-4.438023],[12.995517,-4.781103],[12.631612,-4.991271],[12.468004,-5.248362],[12.436688,-5.684304]]]]},"id":"AGO"},
{"type":"Feature","properties":{"name":"Albania"},"geometry":{"type":"Polygon","coordinates":[[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]},"id":"ALB"},
{"type":"Feature","properties":{"name":"United Arab Emirates"},"geometry":{"type":"Polygon","coordinates":[[[51.579519,24.245497],[51.757441,24.294073],[51.794389,24.019826],[52.577081,24.177439],[53.404007,24.151317],[54.008001,24.121758],[54.693024,24.797892],[55.439025,25.439145],[56.070821,26.055464],[56.261042,25.714606],[56.396847,24.924732],[55.886233,24.920831],[55.804119,24.269604],[55.981214,24.130543],[55.528632,23.933604],[55.525841,23.524869],[55.234489,23.110993],[55.208341,22.70833],[55.006803,22.496948],[52.000733,23.001154],[51.617708,24.014219],[51.579519,24.245497]]]},"id":"ARE"},
{"type":"Feature","properties":{"name":"Argentina"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-65.5,-55.2],[-66.45,-55.25],[-66.95992,-54.89681],[-67.56244,-54.87001],[-68.63335,-54.8695],[-68.63401,-52.63637],[-68.25,-53.1],[-67.75,-53.85],[-66.45,-54.45],[-65.05,-54.7],[-65.5,-55.2]]],[[[-64.964892,-22.075862],[-64.377021,-22.798091],[-63.986838,-21.993644],[-62.846468,-22.034985],[-62.685057,-22.249029],[-60.846565,-23.880713],[-60.028966,-24.032796],[-58.807128,-24.771459],[-57.777217,-25.16234],[-57.63366,-25.603657],[-58.618174,-27.123719],[-57.60976,-27.395899],[-56.486702,-27.548499],[-55.695846,-27.387837],[-54.788795,-26.621786],[-54.625291,-25.739255],[-54.13005,-25.547639],[-53.628349,-26.124865],[-53.648735,-26.923473],[-54.490725,-27.474757],[-55.162286,-27.881915],[-56.2909,-28.852761],[-57.625133,-30.216295],[-57.874937,-31.016556],[-58.14244,-32.044504],[-58.132648,-33.040567],[-58.349611,-33.263189],[-58.427074,-33.909454],[-58.495442,-34.43149],[-57.22583,-35.288027],[-57.362359,-35.97739],[-56.737487,-36.413126],[-56.788285,-36.901572],[-57.749157,-38.183871],[-59.231857,-38.72022],[-61.237445,-38.928425],[-62.335957,-38.827707],[-62.125763,-39.424105],[-62.330531,-40.172586],[-62.145994,-40.676897],[-62.745803,-41.028761],[-63.770495,-41.166789],[-64.73209,-40.802677],[-65.118035,-41.064315],[-64.978561,-42.058001],[-64.303408,-42.359016],[-63.755948,-42.043687],[-63.458059,-42.563138],[-64.378804,-42.873558],[-65.181804,-43.495381],[-65.328823,-44.501366],[-65.565269,-45.036786],[-66.509966,-45.039628],[-67.293794,-45.551896],[-67.580546,-46.301773],[-66.597066,-47.033925],[-65.641027,-47.236135],[-65.985088,-48.133289],[-67.166179,-48.697337],[-67.816088,-49.869669],[-68.728745,-50.264218],[-69.138539,-50.73251],[-68.815561,-51.771104],[-68.149995,-52.349983],[-68.571545,-52.299444],[-69.498362,-52.142761],[-71.914804,-52.009022],[-72.329404,-51.425956],[-72.309974,-50.67701],[-72.975747,-50.74145],[-73.328051,-50.378785],[-73.415436,-49.318436],[-72.648247,-48.878618],[-72.331161,-48.244238],[-72.447355,-47.738533],[-71.917258,-46.884838],[-71.552009,-45.560733],[-71.659316,-44.973689],[-71.222779,-44.784243],[-71.329801,-44.407522],[-71.793623,-44.207172],[-71.464056,-43.787611],[-71.915424,-43.408565],[-72.148898,-42.254888],[-71.746804,-42.051386],[-71.915734,-40.832339],[-71.680761,-39.808164],[-71.413517,-38.916022],[-70.814664,-38.552995],[-71.118625,-37.576827],[-71.121881,-36.658124],[-70.364769,-36.005089],[-70.388049,-35.169688],[-69.817309,-34.193571],[-69.814777,-33.273886],[-70.074399,-33.09121],[-70.535069,-31.36501],[-69.919008,-30.336339],[-70.01355,-29.367923],[-69.65613,-28.459141],[-69.001235,-27.521214],[-68.295542,-26.89934],[-68.5948,-26.506909],[-68.386001,-26.185016],[-68.417653,-24.518555],[-67.328443,-24.025303],[-66.985234,-22.986349],[-67.106674,-22.735925],[-66.273339,-21.83231],[-64.964892,-22.075862]]]]},"id":"ARG"},
{"type":"Feature","properties":{"name":"Armenia"},"geometry":{"type":"Polygon","coordinates":[[[43.582746,41.092143],[44.97248,41.248129],[45.179496,40.985354],[45.560351,40.81229],[45.359175,40.561504],[45.891907,40.218476],[45.610012,39.899994],[46.034534,39.628021],[46.483499,39.464155],[46.50572,38.770605],[46.143623,38.741201],[45.735379,39.319719],[45.739978,39.473999],[45.298145,39.471751],[45.001987,39.740004],[44.79399,39.713003],[44.400009,40.005],[43.656436,40.253564],[43.752658,40.740201],[43.582746,41.092143]]]},"id":"ARM"},
{"type":"Feature","properties":{"name":"Antarctica"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-59.572095,-80.040179],[-59.865849,-80.549657],[-60.159656,-81.000327],[-62.255393,-80.863178],[-64.488125,-80.921934],[-65.741666,-80.588827],[-65.741666,-80.549657],[-66.290031,-80.255773],[-64.037688,-80.294944],[-61.883246,-80.39287],[-61.138976,-79.981371],[-60.610119,-79.628679],[-59.572095,-80.040179]]],[[[-159.208184,-79.497059],[-161.127601,-79.634209],[-162.439847,-79.281465],[-163.027408,-78.928774],[-163.066604,-78.869966],[-163.712896,-78.595667],[-163.712896,-78.595667],[-163.105801,-78.223338],[-161.245113,-78.380176],[-160.246208,-78.693645],[-159.482405,-79.046338],[-159.208184,-79.497059]]],[[[-45.154758,-78.04707],[-43.920828,-78.478103],[-43.48995,-79.08556],[-43.372438,-79.516645],[-43.333267,-80.026123],[-44.880537,-80.339644],[-46.506174,-80.594357],[-48.386421,-80.829485],[-50.482107,-81.025442],[-52.851988,-80.966685],[-54.164259,-80.633528],[-53.987991,-80.222028],[-51.853134,-79.94773],[-50.991326,-79.614623],[-50.364595,-79.183487],[-49.914131,-78.811209],[-49.306959,-78.458569],[-48.660616,-78.047018],[-48.660616,-78.047019],[-48.151396,-78.04707],[-46.662857,-77.831476],[-45.154758,-78.04707]]],[[[-121.211511,-73.50099],[-119.918851,-73.657725],[-118.724143,-73.481353],[-119.292119,-73.834097],[-120.232217,-74.08881],[-121.62283,-74.010468],[-122.621735,-73.657778],[-122.621735,-73.657777],[-122.406245,-73.324619],[-121.211511,-73.50099]]],[[[-125.559566,-73.481353],[-124.031882,-73.873268],[-124.619469,-73.834097],[-125.912181,-73.736118],[-127.28313,-73.461769],[-127.28313,-73.461768],[-126.558472,-73.246226],[-125.559566,-73.481353]]],[[[-98.98155,-71.933334],[-97.884743,-72.070535],[-96.787937,-71.952971],[-96.20035,-72.521205],[-96.983765,-72.442864],[-98.198083,-72.482035],[-99.432013,-72.442864],[-100.783455,-72.50162],[-101.801868,-72.305663],[-102.330725,-71.894164],[-102.330725,-71.894164],[-101.703967,-71.717792],[-100.430919,-71.854993],[-98.98155,-71.933334]]],[[[-68.451346,-70.955823],[-68.333834,-71.406493],[-68.510128,-71.798407],[-68.784297,-72.170736],[-69.959471,-72.307885],[-71.075889,-72.503842],[-72.388134,-72.484257],[-71.8985,-72.092343],[-73.073622,-72.229492],[-74.19004,-72.366693],[-74.953895,-72.072757],[-75.012625,-71.661258],[-73.915819,-71.269345],[-73.915819,-71.269344],[-73.230331,-71.15178],[-72.074717,-71.190951],[-71.780962,-70.681473],[-71.72218,-70.309196],[-71.741791,-69.505782],[-71.173815,-69.035475],[-70.253252,-68.87874],[-69.724447,-69.251017],[-69.489422,-69.623346],[-69.058518,-70.074016],[-68.725541,-70.505153],[-68.451346,-70.955823]]],[[[-58.614143,-64.152467],[-59.045073,-64.36801],[-59.789342,-64.211223],[-60.611928,-64.309202],[-61.297416,-64.54433],[-62.0221,-64.799094],[-62.51176,-65.09303],[-62.648858,-65.484942],[-62.590128,-65.857219],[-62.120079,-66.190326],[-62.805567,-66.425505],[-63.74569,-66.503847],[-64.294106,-66.837004],[-64.881693,-67.150474],[-65.508425,-67.58161],[-65.665082,-67.953887],[-65.312545,-68.365335],[-64.783715,-68.678908],[-63.961103,-68.913984],[-63.1973,-69.227556],[-62.785955,-69.619419],[-62.570516,-69.991747],[-62.276736,-70.383661],[-61.806661,-70.716768],[-61.512906,-71.089045],[-61.375809,-72.010074],[-61.081977,-72.382351],[-61.003661,-72.774265],[-60.690269,-73.166179],[-60.827367,-73.695242],[-61.375809,-74.106742],[-61.96337,-74.439848],[-63.295201,-74.576997],[-63.74569,-74.92974],[-64.352836,-75.262847],[-65.860987,-75.635124],[-67.192818,-75.79191],[-68.446282,-76.007452],[-69.797724,-76.222995],[-70.600724,-76.634494],[-72.206776,-76.673665],[-73.969536,-76.634494],[-75.555977,-76.712887],[-77.24037,-76.712887],[-76.926979,-77.104802],[-75.399294,-77.28107],[-74.282876,-77.55542],[-73.656119,-77.908112],[-74.772536,-78.221633],[-76.4961,-78.123654],[-77.925858,-78.378419],[-77.984666,-78.789918],[-78.023785,-79.181833],[-76.848637,-79.514939],[-76.633224,-79.887216],[-75.360097,-80.259545],[-73.244852,-80.416331],[-71.442946,-80.69063],[-70.013163,-81.004151],[-68.191646,-81.317672],[-65.704279,-81.474458],[-63.25603,-81.748757],[-61.552026,-82.042692],[-59.691416,-82.37585],[-58.712121,-82.846106],[-58.222487,-83.218434],[-57.008117,-82.865691],[-55.362894,-82.571755],[-53.619771,-82.258235],[-51.543644,-82.003521],[-49.76135,-81.729171],[-47.273931,-81.709586],[-44.825708,-81.846735],[-42.808363,-82.081915],[-42.16202,-81.65083],[-40.771433,-81.356894],[-38.244818,-81.337309],[-36.26667,-81.121715],[-34.386397,-80.906172],[-32.310296,-80.769023],[-30.097098,-80.592651],[-28.549802,-80.337938],[-29.254901,-79.985195],[-29.685805,-79.632503],[-29.685805,-79.260226],[-31.624808,-79.299397],[-33.681324,-79.456132],[-35.639912,-79.456132],[-35.914107,-79.083855],[-35.77701,-78.339248],[-35.326546,-78.123654],[-33.896763,-77.888526],[-32.212369,-77.65345],[-30.998051,-77.359515],[-29.783732,-77.065579],[-28.882779,-76.673665],[-27.511752,-76.497345],[-26.160336,-76.360144],[-25.47482Line truncated
{"type":"Feature","properties":{"name":"French Southern and Antarctic Lands"},"geometry":{"type":"Polygon","coordinates":[[[68.935,-48.625],[69.58,-48.94],[70.525,-49.065],[70.56,-49.255],[70.28,-49.71],[68.745,-49.775],[68.72,-49.2425],[68.8675,-48.83],[68.935,-48.625]]]},"id":"ATF"},
{"type":"Feature","properties":{"name":"Australia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[145.397978,-40.792549],[146.364121,-41.137695],[146.908584,-41.000546],[147.689259,-40.808258],[148.289068,-40.875438],[148.359865,-42.062445],[148.017301,-42.407024],[147.914052,-43.211522],[147.564564,-42.937689],[146.870343,-43.634597],[146.663327,-43.580854],[146.048378,-43.549745],[145.43193,-42.693776],[145.29509,-42.03361],[144.718071,-41.162552],[144.743755,-40.703975],[145.397978,-40.792549]]],[[[143.561811,-13.763656],[143.922099,-14.548311],[144.563714,-14.171176],[144.894908,-14.594458],[145.374724,-14.984976],[145.271991,-15.428205],[145.48526,-16.285672],[145.637033,-16.784918],[145.888904,-16.906926],[146.160309,-17.761655],[146.063674,-18.280073],[146.387478,-18.958274],[147.471082,-19.480723],[148.177602,-19.955939],[148.848414,-20.39121],[148.717465,-20.633469],[149.28942,-21.260511],[149.678337,-22.342512],[150.077382,-22.122784],[150.482939,-22.556142],[150.727265,-22.402405],[150.899554,-23.462237],[151.609175,-24.076256],[152.07354,-24.457887],[152.855197,-25.267501],[153.136162,-26.071173],[153.161949,-26.641319],[153.092909,-27.2603],[153.569469,-28.110067],[153.512108,-28.995077],[153.339095,-29.458202],[153.069241,-30.35024],[153.089602,-30.923642],[152.891578,-31.640446],[152.450002,-32.550003],[151.709117,-33.041342],[151.343972,-33.816023],[151.010555,-34.31036],[150.714139,-35.17346],[150.32822,-35.671879],[150.075212,-36.420206],[149.946124,-37.109052],[149.997284,-37.425261],[149.423882,-37.772681],[148.304622,-37.809061],[147.381733,-38.219217],[146.922123,-38.606532],[146.317922,-39.035757],[145.489652,-38.593768],[144.876976,-38.417448],[145.032212,-37.896188],[144.485682,-38.085324],[143.609974,-38.809465],[142.745427,-38.538268],[142.17833,-38.380034],[141.606582,-38.308514],[140.638579,-38.019333],[139.992158,-37.402936],[139.806588,-36.643603],[139.574148,-36.138362],[139.082808,-35.732754],[138.120748,-35.612296],[138.449462,-35.127261],[138.207564,-34.384723],[137.71917,-35.076825],[136.829406,-35.260535],[137.352371,-34.707339],[137.503886,-34.130268],[137.890116,-33.640479],[137.810328,-32.900007],[136.996837,-33.752771],[136.372069,-34.094766],[135.989043,-34.890118],[135.208213,-34.47867],[135.239218,-33.947953],[134.613417,-33.222778],[134.085904,-32.848072],[134.273903,-32.617234],[132.990777,-32.011224],[132.288081,-31.982647],[131.326331,-31.495803],[129.535794,-31.590423],[128.240938,-31.948489],[127.102867,-32.282267],[126.148714,-32.215966],[125.088623,-32.728751],[124.221648,-32.959487],[124.028947,-33.483847],[123.659667,-33.890179],[122.811036,-33.914467],[122.183064,-34.003402],[121.299191,-33.821036],[120.580268,-33.930177],[119.893695,-33.976065],[119.298899,-34.509366],[119.007341,-34.464149],[118.505718,-34.746819],[118.024972,-35.064733],[117.295507,-35.025459],[116.625109,-35.025097],[115.564347,-34.386428],[115.026809,-34.196517],[115.048616,-33.623425],[115.545123,-33.487258],[115.714674,-33.259572],[115.679379,-32.900369],[115.801645,-32.205062],[115.689611,-31.612437],[115.160909,-30.601594],[114.997043,-30.030725],[115.040038,-29.461095],[114.641974,-28.810231],[114.616498,-28.516399],[114.173579,-28.118077],[114.048884,-27.334765],[113.477498,-26.543134],[113.338953,-26.116545],[113.778358,-26.549025],[113.440962,-25.621278],[113.936901,-25.911235],[114.232852,-26.298446],[114.216161,-25.786281],[113.721255,-24.998939],[113.625344,-24.683971],[113.393523,-24.384764],[113.502044,-23.80635],[113.706993,-23.560215],[113.843418,-23.059987],[113.736552,-22.475475],[114.149756,-21.755881],[114.225307,-22.517488],[114.647762,-21.82952],[115.460167,-21.495173],[115.947373,-21.068688],[116.711615,-20.701682],[117.166316,-20.623599],[117.441545,-20.746899],[118.229559,-20.374208],[118.836085,-20.263311],[118.987807,-20.044203],[119.252494,-19.952942],[119.805225,-19.976506],[120.85622,-19.683708],[121.399856,-19.239756],[121.655138,-18.705318],[122.241665,-18.197649],[122.286624,-17.798603],[122.312772,-17.254967],[123.012574,-16.4052],[123.433789,-17.268558],[123.859345,-17.069035],[123.503242,-16.596506],[123.817073,-16.111316],[124.258287,-16.327944],[124.379726,-15.56706],[124.926153,-15.0751],[125.167275,-14.680396],[125.670087,-14.51007],[125.685796,-14.230656],[126.125149,-14.347341],[126.142823,-14.095987],[126.582589,-13.952791],[127.065867,-13.817968],[127.804633,-14.276906],[128.35969,-14.86917],[128.985543,-14.875991],[129.621473,-14.969784],[129.4096,-14.42067],[129.888641,-13.618703],[130.339466,-13.357376],[130.183506,-13.10752],[130.617795,-12.536392],[131.223495,-12.183649],[131.735091,-12.302453],[132.575298,-12.114041],[132.557212,-11.603012],[131.824698,-11.273782],[132.357224,-11.128519],[133.019561,-11.376411],[133.550846,-11.786515],[134.393068,-12.042365],[134.678632,-11.941183],[135.298491,-12.248606],[135.882693,-11.962267],[136.258381,-12.049342],[136.492475,-11.857209],[136.95162,-12.351959],[136.685125,-12.887223],[136Line truncated
{"type":"Feature","properties":{"name":"Austria"},"geometry":{"type":"Polygon","coordinates":[[[16.979667,48.123497],[16.903754,47.714866],[16.340584,47.712902],[16.534268,47.496171],[16.202298,46.852386],[16.011664,46.683611],[15.137092,46.658703],[14.632472,46.431817],[13.806475,46.509306],[12.376485,46.767559],[12.153088,47.115393],[11.164828,46.941579],[11.048556,46.751359],[10.442701,46.893546],[9.932448,46.920728],[9.47997,47.10281],[9.632932,47.347601],[9.594226,47.525058],[9.896068,47.580197],[10.402084,47.302488],[10.544504,47.566399],[11.426414,47.523766],[12.141357,47.703083],[12.62076,47.672388],[12.932627,47.467646],[13.025851,47.637584],[12.884103,48.289146],[13.243357,48.416115],[13.595946,48.877172],[14.338898,48.555305],[14.901447,48.964402],[15.253416,49.039074],[16.029647,48.733899],[16.499283,48.785808],[16.960288,48.596982],[16.879983,48.470013],[16.979667,48.123497]]]},"id":"AUT"},
{"type":"Feature","properties":{"name":"Azerbaijan"},"geometry":{"type":"MultiPolygon","coordinates":[[[[45.001987,39.740004],[45.298145,39.471751],[45.739978,39.473999],[45.735379,39.319719],[46.143623,38.741201],[45.457722,38.874139],[44.952688,39.335765],[44.79399,39.713003],[45.001987,39.740004]]],[[[47.373315,41.219732],[47.815666,41.151416],[47.987283,41.405819],[48.584353,41.80887],[49.110264,41.282287],[49.618915,40.572924],[50.08483,40.526157],[50.392821,40.256561],[49.569202,40.176101],[49.395259,39.399482],[49.223228,39.049219],[48.856532,38.815486],[48.883249,38.320245],[48.634375,38.270378],[48.010744,38.794015],[48.355529,39.288765],[48.060095,39.582235],[47.685079,39.508364],[46.50572,38.770605],[46.483499,39.464155],[46.034534,39.628021],[45.610012,39.899994],[45.891907,40.218476],[45.359175,40.561504],[45.560351,40.81229],[45.179496,40.985354],[44.97248,41.248129],[45.217426,41.411452],[45.962601,41.123873],[46.501637,41.064445],[46.637908,41.181673],[46.145432,41.722802],[46.404951,41.860675],[46.686071,41.827137],[47.373315,41.219732]]]]},"id":"AZE"},
{"type":"Feature","properties":{"name":"Burundi"},"geometry":{"type":"Polygon","coordinates":[[[29.339998,-4.499983],[29.276384,-3.293907],[29.024926,-2.839258],[29.632176,-2.917858],[29.938359,-2.348487],[30.469696,-2.413858],[30.527677,-2.807632],[30.743013,-3.034285],[30.752263,-3.35933],[30.50556,-3.568567],[30.116333,-4.090138],[29.753512,-4.452389],[29.339998,-4.499983]]]},"id":"BDI"},
{"type":"Feature","properties":{"name":"Belgium"},"geometry":{"type":"Polygon","coordinates":[[[3.314971,51.345781],[4.047071,51.267259],[4.973991,51.475024],[5.606976,51.037298],[6.156658,50.803721],[6.043073,50.128052],[5.782417,50.090328],[5.674052,49.529484],[4.799222,49.985373],[4.286023,49.907497],[3.588184,50.378992],[3.123252,50.780363],[2.658422,50.796848],[2.513573,51.148506],[3.314971,51.345781]]]},"id":"BEL"},
{"type":"Feature","properties":{"name":"Benin"},"geometry":{"type":"Polygon","coordinates":[[[2.691702,6.258817],[1.865241,6.142158],[1.618951,6.832038],[1.664478,9.12859],[1.463043,9.334624],[1.425061,9.825395],[1.077795,10.175607],[0.772336,10.470808],[0.899563,10.997339],[1.24347,11.110511],[1.447178,11.547719],[1.935986,11.64115],[2.154474,11.94015],[2.490164,12.233052],[2.848643,12.235636],[3.61118,11.660167],[3.572216,11.327939],[3.797112,10.734746],[3.60007,10.332186],[3.705438,10.06321],[3.220352,9.444153],[2.912308,9.137608],[2.723793,8.506845],[2.749063,7.870734],[2.691702,6.258817]]]},"id":"BEN"},
{"type":"Feature","properties":{"name":"Burkina Faso"},"geometry":{"type":"Polygon","coordinates":[[[-2.827496,9.642461],[-3.511899,9.900326],[-3.980449,9.862344],[-4.330247,9.610835],[-4.779884,9.821985],[-4.954653,10.152714],[-5.404342,10.370737],[-5.470565,10.95127],[-5.197843,11.375146],[-5.220942,11.713859],[-4.427166,12.542646],[-4.280405,13.228444],[-4.006391,13.472485],[-3.522803,13.337662],[-3.103707,13.541267],[-2.967694,13.79815],[-2.191825,14.246418],[-2.001035,14.559008],[-1.066363,14.973815],[-0.515854,15.116158],[-0.266257,14.924309],[0.374892,14.928908],[0.295646,14.444235],[0.429928,13.988733],[0.993046,13.33575],[1.024103,12.851826],[2.177108,12.625018],[2.154474,11.94015],[1.935986,11.64115],[1.447178,11.547719],[1.24347,11.110511],[0.899563,10.997339],[0.023803,11.018682],[-0.438702,11.098341],[-0.761576,10.93693],[-1.203358,11.009819],[-2.940409,10.96269],[-2.963896,10.395335],[-2.827496,9.642461]]]},"id":"BFA"},
{"type":"Feature","properties":{"name":"Bangladesh"},"geometry":{"type":"Polygon","coordinates":[[[92.672721,22.041239],[92.652257,21.324048],[92.303234,21.475485],[92.368554,20.670883],[92.082886,21.192195],[92.025215,21.70157],[91.834891,22.182936],[91.417087,22.765019],[90.496006,22.805017],[90.586957,22.392794],[90.272971,21.836368],[89.847467,22.039146],[89.70205,21.857116],[89.418863,21.966179],[89.031961,22.055708],[88.876312,22.879146],[88.52977,23.631142],[88.69994,24.233715],[88.084422,24.501657],[88.306373,24.866079],[88.931554,25.238692],[88.209789,25.768066],[88.563049,26.446526],[89.355094,26.014407],[89.832481,25.965082],[89.920693,25.26975],[90.872211,25.132601],[91.799596,25.147432],[92.376202,24.976693],[91.915093,24.130414],[91.46773,24.072639],[91.158963,23.503527],[91.706475,22.985264],[91.869928,23.624346],[92.146035,23.627499],[92.672721,22.041239]]]},"id":"BGD"},
{"type":"Feature","properties":{"name":"Bulgaria"},"geometry":{"type":"Polygon","coordinates":[[[22.65715,44.234923],[22.944832,43.823785],[23.332302,43.897011],[24.100679,43.741051],[25.569272,43.688445],[26.065159,43.943494],[27.2424,44.175986],[27.970107,43.812468],[28.558081,43.707462],[28.039095,43.293172],[27.673898,42.577892],[27.99672,42.007359],[27.135739,42.141485],[26.117042,41.826905],[26.106138,41.328899],[25.197201,41.234486],[24.492645,41.583896],[23.692074,41.309081],[22.952377,41.337994],[22.881374,41.999297],[22.380526,42.32026],[22.545012,42.461362],[22.436595,42.580321],[22.604801,42.898519],[22.986019,43.211161],[22.500157,43.642814],[22.410446,44.008063],[22.65715,44.234923]]]},"id":"BGR"},
{"type":"Feature","properties":{"name":"The Bahamas"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-77.53466,23.75975],[-77.78,23.71],[-78.03405,24.28615],[-78.40848,24.57564],[-78.19087,25.2103],[-77.89,25.17],[-77.54,24.34],[-77.53466,23.75975]]],[[[-77.82,26.58],[-78.91,26.42],[-78.98,26.79],[-78.51,26.87],[-77.85,26.84],[-77.82,26.58]]],[[[-77,26.59],[-77.17255,25.87918],[-77.35641,26.00735],[-77.34,26.53],[-77.78802,26.92516],[-77.79,27.04],[-77,26.59]]]]},"id":"BHS"},
{"type":"Feature","properties":{"name":"Bosnia and Herzegovina"},"geometry":{"type":"Polygon","coordinates":[[[19.005486,44.860234],[19.36803,44.863],[19.11761,44.42307],[19.59976,44.03847],[19.454,43.5681],[19.21852,43.52384],[19.03165,43.43253],[18.70648,43.20011],[18.56,42.65],[17.674922,43.028563],[17.297373,43.446341],[16.916156,43.667722],[16.456443,44.04124],[16.23966,44.351143],[15.750026,44.818712],[15.959367,45.233777],[16.318157,45.004127],[16.534939,45.211608],[17.002146,45.233777],[17.861783,45.06774],[18.553214,45.08159],[19.005486,44.860234]]]},"id":"BIH"},
{"type":"Feature","properties":{"name":"Belarus"},"geometry":{"type":"Polygon","coordinates":[[[23.484128,53.912498],[24.450684,53.905702],[25.536354,54.282423],[25.768433,54.846963],[26.588279,55.167176],[26.494331,55.615107],[27.10246,55.783314],[28.176709,56.16913],[29.229513,55.918344],[29.371572,55.670091],[29.896294,55.789463],[30.873909,55.550976],[30.971836,55.081548],[30.757534,54.811771],[31.384472,54.157056],[31.791424,53.974639],[31.731273,53.794029],[32.405599,53.618045],[32.693643,53.351421],[32.304519,53.132726],[31.497644,53.167427],[31.305201,53.073996],[31.540018,52.742052],[31.785998,52.101678],[30.927549,52.042353],[30.619454,51.822806],[30.555117,51.319503],[30.157364,51.416138],[29.254938,51.368234],[28.992835,51.602044],[28.617613,51.427714],[28.241615,51.572227],[27.454066,51.592303],[26.337959,51.832289],[25.327788,51.910656],[24.553106,51.888461],[24.005078,51.617444],[23.527071,51.578454],[23.508002,52.023647],[23.199494,52.486977],[23.799199,52.691099],[23.804935,53.089731],[23.527536,53.470122],[23.484128,53.912498]]]},"id":"BLR"},
{"type":"Feature","properties":{"name":"Belize"},"geometry":{"type":"Polygon","coordinates":[[[-89.14308,17.808319],[-89.150909,17.955468],[-89.029857,18.001511],[-88.848344,17.883198],[-88.490123,18.486831],[-88.300031,18.499982],[-88.296336,18.353273],[-88.106813,18.348674],[-88.123479,18.076675],[-88.285355,17.644143],[-88.197867,17.489475],[-88.302641,17.131694],[-88.239518,17.036066],[-88.355428,16.530774],[-88.551825,16.265467],[-88.732434,16.233635],[-88.930613,15.887273],[-89.229122,15.886938],[-89.150806,17.015577],[-89.14308,17.808319]]]},"id":"BLZ"},
{"type":"Feature","properties":{"name":"Bolivia"},"geometry":{"type":"Polygon","coordinates":[[[-62.846468,-22.034985],[-63.986838,-21.993644],[-64.377021,-22.798091],[-64.964892,-22.075862],[-66.273339,-21.83231],[-67.106674,-22.735925],[-67.82818,-22.872919],[-68.219913,-21.494347],[-68.757167,-20.372658],[-68.442225,-19.405068],[-68.966818,-18.981683],[-69.100247,-18.260125],[-69.590424,-17.580012],[-68.959635,-16.500698],[-69.389764,-15.660129],[-69.160347,-15.323974],[-69.339535,-14.953195],[-68.948887,-14.453639],[-68.929224,-13.602684],[-68.88008,-12.899729],[-68.66508,-12.5613],[-69.529678,-10.951734],[-68.786158,-11.03638],[-68.271254,-11.014521],[-68.048192,-10.712059],[-67.173801,-10.306812],[-66.646908,-9.931331],[-65.338435,-9.761988],[-65.444837,-10.511451],[-65.321899,-10.895872],[-65.402281,-11.56627],[-64.316353,-12.461978],[-63.196499,-12.627033],[-62.80306,-13.000653],[-62.127081,-13.198781],[-61.713204,-13.489202],[-61.084121,-13.479384],[-60.503304,-13.775955],[-60.459198,-14.354007],[-60.264326,-14.645979],[-60.251149,-15.077219],[-60.542966,-15.09391],[-60.15839,-16.258284],[-58.24122,-16.299573],[-58.388058,-16.877109],[-58.280804,-17.27171],[-57.734558,-17.552468],[-57.498371,-18.174188],[-57.676009,-18.96184],[-57.949997,-19.400004],[-57.853802,-19.969995],[-58.166392,-20.176701],[-58.183471,-19.868399],[-59.115042,-19.356906],[-60.043565,-19.342747],[-61.786326,-19.633737],[-62.265961,-20.513735],[-62.291179,-21.051635],[-62.685057,-22.249029],[-62.846468,-22.034985]]]},"id":"BOL"},
{"type":"Feature","properties":{"name":"Brazil"},"geometry":{"type":"Polygon","coordinates":[[[-57.625133,-30.216295],[-56.2909,-28.852761],[-55.162286,-27.881915],[-54.490725,-27.474757],[-53.648735,-26.923473],[-53.628349,-26.124865],[-54.13005,-25.547639],[-54.625291,-25.739255],[-54.428946,-25.162185],[-54.293476,-24.5708],[-54.29296,-24.021014],[-54.652834,-23.839578],[-55.027902,-24.001274],[-55.400747,-23.956935],[-55.517639,-23.571998],[-55.610683,-22.655619],[-55.797958,-22.35693],[-56.473317,-22.0863],[-56.88151,-22.282154],[-57.937156,-22.090176],[-57.870674,-20.732688],[-58.166392,-20.176701],[-57.853802,-19.969995],[-57.949997,-19.400004],[-57.676009,-18.96184],[-57.498371,-18.174188],[-57.734558,-17.552468],[-58.280804,-17.27171],[-58.388058,-16.877109],[-58.24122,-16.299573],[-60.15839,-16.258284],[-60.542966,-15.09391],[-60.251149,-15.077219],[-60.264326,-14.645979],[-60.459198,-14.354007],[-60.503304,-13.775955],[-61.084121,-13.479384],[-61.713204,-13.489202],[-62.127081,-13.198781],[-62.80306,-13.000653],[-63.196499,-12.627033],[-64.316353,-12.461978],[-65.402281,-11.56627],[-65.321899,-10.895872],[-65.444837,-10.511451],[-65.338435,-9.761988],[-66.646908,-9.931331],[-67.173801,-10.306812],[-68.048192,-10.712059],[-68.271254,-11.014521],[-68.786158,-11.03638],[-69.529678,-10.951734],[-70.093752,-11.123972],[-70.548686,-11.009147],[-70.481894,-9.490118],[-71.302412,-10.079436],[-72.184891,-10.053598],[-72.563033,-9.520194],[-73.226713,-9.462213],[-73.015383,-9.032833],[-73.571059,-8.424447],[-73.987235,-7.52383],[-73.723401,-7.340999],[-73.724487,-6.918595],[-73.120027,-6.629931],[-73.219711,-6.089189],[-72.964507,-5.741251],[-72.891928,-5.274561],[-71.748406,-4.593983],[-70.928843,-4.401591],[-70.794769,-4.251265],[-69.893635,-4.298187],[-69.444102,-1.556287],[-69.420486,-1.122619],[-69.577065,-0.549992],[-70.020656,-0.185156],[-70.015566,0.541414],[-69.452396,0.706159],[-69.252434,0.602651],[-69.218638,0.985677],[-69.804597,1.089081],[-69.816973,1.714805],[-67.868565,1.692455],[-67.53781,2.037163],[-67.259998,1.719999],[-67.065048,1.130112],[-66.876326,1.253361],[-66.325765,0.724452],[-65.548267,0.789254],[-65.354713,1.095282],[-64.611012,1.328731],[-64.199306,1.492855],[-64.083085,1.916369],[-63.368788,2.2009],[-63.422867,2.411068],[-64.269999,2.497006],[-64.408828,3.126786],[-64.368494,3.79721],[-64.816064,4.056445],[-64.628659,4.148481],[-63.888343,4.02053],[-63.093198,3.770571],[-62.804533,4.006965],[-62.08543,4.162124],[-60.966893,4.536468],[-60.601179,4.918098],[-60.733574,5.200277],[-60.213683,5.244486],[-59.980959,5.014061],[-60.111002,4.574967],[-59.767406,4.423503],[-59.53804,3.958803],[-59.815413,3.606499],[-59.974525,2.755233],[-59.718546,2.24963],[-59.646044,1.786894],[-59.030862,1.317698],[-58.540013,1.268088],[-58.429477,1.463942],[-58.11345,1.507195],[-57.660971,1.682585],[-57.335823,1.948538],[-56.782704,1.863711],[-56.539386,1.899523],[-55.995698,1.817667],[-55.9056,2.021996],[-56.073342,2.220795],[-55.973322,2.510364],[-55.569755,2.421506],[-55.097587,2.523748],[-54.524754,2.311849],[-54.088063,2.105557],[-53.778521,2.376703],[-53.554839,2.334897],[-53.418465,2.053389],[-52.939657,2.124858],[-52.556425,2.504705],[-52.249338,3.241094],[-51.657797,4.156232],[-51.317146,4.203491],[-51.069771,3.650398],[-50.508875,1.901564],[-49.974076,1.736483],[-49.947101,1.04619],[-50.699251,0.222984],[-50.388211,-0.078445],[-48.620567,-0.235489],[-48.584497,-1.237805],[-47.824956,-0.581618],[-46.566584,-0.941028],[-44.905703,-1.55174],[-44.417619,-2.13775],[-44.581589,-2.691308],[-43.418791,-2.38311],[-41.472657,-2.912018],[-39.978665,-2.873054],[-38.500383,-3.700652],[-37.223252,-4.820946],[-36.452937,-5.109404],[-35.597796,-5.149504],[-35.235389,-5.464937],[-34.89603,-6.738193],[-34.729993,-7.343221],[-35.128212,-8.996401],[-35.636967,-9.649282],[-37.046519,-11.040721],[-37.683612,-12.171195],[-38.423877,-13.038119],[-38.673887,-13.057652],[-38.953276,-13.79337],[-38.882298,-15.667054],[-39.161092,-17.208407],[-39.267339,-17.867746],[-39.583521,-18.262296],[-39.760823,-19.599113],[-40.774741,-20.904512],[-40.944756,-21.937317],[-41.754164,-22.370676],[-41.988284,-22.97007],[-43.074704,-22.967693],[-44.647812,-23.351959],[-45.352136,-23.796842],[-46.472093,-24.088969],[-47.648972,-24.885199],[-48.495458,-25.877025],[-48.641005,-26.623698],[-48.474736,-27.175912],[-48.66152,-28.186135],[-48.888457,-28.674115],[-49.587329,-29.224469],[-50.696874,-30.984465],[-51.576226,-31.777698],[-52.256081,-32.24537],[-52.7121,-33.196578],[-53.373662,-33.768378],[-53.650544,-33.202004],[-53.209589,-32.727666],[-53.787952,-32.047243],[-54.572452,-31.494511],[-55.60151,-30.853879],[-55.973245,-30.883076],[-56.976026,-30.109686],[-57.625133,-30.216295]]]},"id":"BRA"},
{"type":"Feature","properties":{"name":"Brunei"},"geometry":{"type":"Polygon","coordinates":[[[114.204017,4.525874],[114.599961,4.900011],[115.45071,5.44773],[115.4057,4.955228],[115.347461,4.316636],[114.869557,4.348314],[114.659596,4.007637],[114.204017,4.525874]]]},"id":"BRN"},
{"type":"Feature","properties":{"name":"Bhutan"},"geometry":{"type":"Polygon","coordinates":[[[91.696657,27.771742],[92.103712,27.452614],[92.033484,26.83831],[91.217513,26.808648],[90.373275,26.875724],[89.744528,26.719403],[88.835643,27.098966],[88.814248,27.299316],[89.47581,28.042759],[90.015829,28.296439],[90.730514,28.064954],[91.258854,28.040614],[91.696657,27.771742]]]},"id":"BTN"},
{"type":"Feature","properties":{"name":"Botswana"},"geometry":{"type":"Polygon","coordinates":[[[25.649163,-18.536026],[25.850391,-18.714413],[26.164791,-19.293086],[27.296505,-20.39152],[27.724747,-20.499059],[27.727228,-20.851802],[28.02137,-21.485975],[28.794656,-21.639454],[29.432188,-22.091313],[28.017236,-22.827754],[27.11941,-23.574323],[26.786407,-24.240691],[26.485753,-24.616327],[25.941652,-24.696373],[25.765849,-25.174845],[25.664666,-25.486816],[25.025171,-25.71967],[24.211267,-25.670216],[23.73357,-25.390129],[23.312097,-25.26869],[22.824271,-25.500459],[22.579532,-25.979448],[22.105969,-26.280256],[21.605896,-26.726534],[20.889609,-26.828543],[20.66647,-26.477453],[20.758609,-25.868136],[20.165726,-24.917962],[19.895768,-24.76779],[19.895458,-21.849157],[20.881134,-21.814327],[20.910641,-18.252219],[21.65504,-18.219146],[23.196858,-17.869038],[23.579006,-18.281261],[24.217365,-17.889347],[24.520705,-17.887125],[25.084443,-17.661816],[25.264226,-17.73654],[25.649163,-18.536026]]]},"id":"BWA"},
{"type":"Feature","properties":{"name":"Central African Republic"},"geometry":{"type":"Polygon","coordinates":[[[15.27946,7.421925],[16.106232,7.497088],[16.290562,7.754307],[16.456185,7.734774],[16.705988,7.508328],[17.96493,7.890914],[18.389555,8.281304],[18.911022,8.630895],[18.81201,8.982915],[19.094008,9.074847],[20.059685,9.012706],[21.000868,9.475985],[21.723822,10.567056],[22.231129,10.971889],[22.864165,11.142395],[22.977544,10.714463],[23.554304,10.089255],[23.55725,9.681218],[23.394779,9.265068],[23.459013,8.954286],[23.805813,8.666319],[24.567369,8.229188],[25.114932,7.825104],[25.124131,7.500085],[25.796648,6.979316],[26.213418,6.546603],[26.465909,5.946717],[27.213409,5.550953],[27.374226,5.233944],[27.044065,5.127853],[26.402761,5.150875],[25.650455,5.256088],[25.278798,5.170408],[25.128833,4.927245],[24.805029,4.897247],[24.410531,5.108784],[23.297214,4.609693],[22.84148,4.710126],[22.704124,4.633051],[22.405124,4.02916],[21.659123,4.224342],[20.927591,4.322786],[20.290679,4.691678],[19.467784,5.031528],[18.932312,4.709506],[18.542982,4.201785],[18.453065,3.504386],[17.8099,3.560196],[17.133042,3.728197],[16.537058,3.198255],[16.012852,2.26764],[15.907381,2.557389],[15.862732,3.013537],[15.405396,3.335301],[15.03622,3.851367],[14.950953,4.210389],[14.478372,4.732605],[14.558936,5.030598],[14.459407,5.451761],[14.53656,6.226959],[14.776545,6.408498],[15.27946,7.421925]]]},"id":"CAF"},
{"type":"Feature","properties":{"name":"Canada"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-63.6645,46.55001],[-62.9393,46.41587],[-62.01208,46.44314],[-62.50391,46.03339],[-62.87433,45.96818],[-64.1428,46.39265],[-64.39261,46.72747],[-64.01486,47.03601],[-63.6645,46.55001]]],[[[-61.806305,49.10506],[-62.29318,49.08717],[-63.58926,49.40069],[-64.51912,49.87304],[-64.17322,49.95718],[-62.85829,49.70641],[-61.835585,49.28855],[-61.806305,49.10506]]],[[[-123.510002,48.510011],[-124.012891,48.370846],[-125.655013,48.825005],[-125.954994,49.179996],[-126.850004,49.53],[-127.029993,49.814996],[-128.059336,49.994959],[-128.444584,50.539138],[-128.358414,50.770648],[-127.308581,50.552574],[-126.695001,50.400903],[-125.755007,50.295018],[-125.415002,49.950001],[-124.920768,49.475275],[-123.922509,49.062484],[-123.510002,48.510011]]],[[[-56.134036,50.68701],[-56.795882,49.812309],[-56.143105,50.150117],[-55.471492,49.935815],[-55.822401,49.587129],[-54.935143,49.313011],[-54.473775,49.556691],[-53.476549,49.249139],[-53.786014,48.516781],[-53.086134,48.687804],[-52.958648,48.157164],[-52.648099,47.535548],[-53.069158,46.655499],[-53.521456,46.618292],[-54.178936,46.807066],[-53.961869,47.625207],[-54.240482,47.752279],[-55.400773,46.884994],[-55.997481,46.91972],[-55.291219,47.389562],[-56.250799,47.632545],[-57.325229,47.572807],[-59.266015,47.603348],[-59.419494,47.899454],[-58.796586,48.251525],[-59.231625,48.523188],[-58.391805,49.125581],[-57.35869,50.718274],[-56.73865,51.287438],[-55.870977,51.632094],[-55.406974,51.588273],[-55.600218,51.317075],[-56.134036,50.68701]]],[[[-132.710008,54.040009],[-132.710009,54.040009],[-132.710008,54.040009],[-132.710008,54.040009],[-131.74999,54.120004],[-132.04948,52.984621],[-131.179043,52.180433],[-131.57783,52.182371],[-132.180428,52.639707],[-132.549992,53.100015],[-133.054611,53.411469],[-133.239664,53.85108],[-133.180004,54.169975],[-132.710008,54.040009]]],[[[-79.26582,62.158675],[-79.65752,61.63308],[-80.09956,61.7181],[-80.36215,62.01649],[-80.315395,62.085565],[-79.92939,62.3856],[-79.52002,62.36371],[-79.26582,62.158675]]],[[[-81.89825,62.7108],[-83.06857,62.15922],[-83.77462,62.18231],[-83.99367,62.4528],[-83.25048,62.91409],[-81.87699,62.90458],[-81.89825,62.7108]]],[[[-85.161308,65.657285],[-84.975764,65.217518],[-84.464012,65.371772],[-83.882626,65.109618],[-82.787577,64.766693],[-81.642014,64.455136],[-81.55344,63.979609],[-80.817361,64.057486],[-80.103451,63.725981],[-80.99102,63.411246],[-82.547178,63.651722],[-83.108798,64.101876],[-84.100417,63.569712],[-85.523405,63.052379],[-85.866769,63.637253],[-87.221983,63.541238],[-86.35276,64.035833],[-86.224886,64.822917],[-85.883848,65.738778],[-85.161308,65.657285]]],[[[-75.86588,67.14886],[-76.98687,67.09873],[-77.2364,67.58809],[-76.81166,68.14856],[-75.89521,68.28721],[-75.1145,68.01036],[-75.10333,67.58202],[-75.21597,67.44425],[-75.86588,67.14886]]],[[[-95.647681,69.10769],[-96.269521,68.75704],[-97.617401,69.06003],[-98.431801,68.9507],[-99.797401,69.40003],[-98.917401,69.71003],[-98.218261,70.14354],[-97.157401,69.86003],[-96.557401,69.68003],[-96.257401,69.49003],[-95.647681,69.10769]]],[[[-90.5471,69.49766],[-90.55151,68.47499],[-89.21515,69.25873],[-88.01966,68.61508],[-88.31749,67.87338],[-87.35017,67.19872],[-86.30607,67.92146],[-85.57664,68.78456],[-85.52197,69.88211],[-84.10081,69.80539],[-82.62258,69.65826],[-81.28043,69.16202],[-81.2202,68.66567],[-81.96436,68.13253],[-81.25928,67.59716],[-81.38653,67.11078],[-83.34456,66.41154],[-84.73542,66.2573],[-85.76943,66.55833],[-86.0676,66.05625],[-87.03143,65.21297],[-87.32324,64.77563],[-88.48296,64.09897],[-89.91444,64.03273],[-90.70398,63.61017],[-90.77004,62.96021],[-91.93342,62.83508],[-93.15698,62.02469],[-94.24153,60.89865],[-94.62931,60.11021],[-94.6846,58.94882],[-93.21502,58.78212],[-92.76462,57.84571],[-92.29703,57.08709],[-90.89769,57.28468],[-89.03953,56.85172],[-88.03978,56.47162],[-87.32421,55.99914],[-86.07121,55.72383],[-85.01181,55.3026],[-83.36055,55.24489],[-82.27285,55.14832],[-82.4362,54.28227],[-82.12502,53.27703],[-81.40075,52.15788],[-79.91289,51.20842],[-79.14301,51.53393],[-78.60191,52.56208],[-79.12421,54.14145],[-79.82958,54.66772],[-78.22874,55.13645],[-77.0956,55.83741],[-76.54137,56.53423],[-76.62319,57.20263],[-77.30226,58.05209],[-78.51688,58.80458],[-77.33676,59.85261],[-77.77272,60.75788],[-78.10687,62.31964],[-77.41067,62.55053],[-75.69621,62.2784],[-74.6682,62.18111],[-73.83988,62.4438],[-72.90853,62.10507],[-71.67708,61.52535],[-71.37369,61.13717],[-69.59042,61.06141],[-69.62033,60.22125],[-69.2879,58.95736],[-68.37455,58.80106],[-67.64976,58.21206],[-66.20178,58.76731],[-65.24517,59.87071],[-64.58352,60.33558],[-63.80475,59.4426],[-62.50236,58.16708],[-61.39655,56.96745],[-61.79866,56.33945],[-60.46853,55.77548],[-59.56962,55.20407],[-57.97508,54.94549],[-57.3332,54.6265],[-56.93689,53.78032],[-56.15811,53.64749],[-55.75632,53.27036],[-55.68338,52.14664],[-56.40916,51.7707],[-57.12691,51.41972],[-58.7Line truncated
{"type":"Feature","properties":{"name":"Switzerland"},"geometry":{"type":"Polygon","coordinates":[[[9.594226,47.525058],[9.632932,47.347601],[9.47997,47.10281],[9.932448,46.920728],[10.442701,46.893546],[10.363378,46.483571],[9.922837,46.314899],[9.182882,46.440215],[8.966306,46.036932],[8.489952,46.005151],[8.31663,46.163642],[7.755992,45.82449],[7.273851,45.776948],[6.843593,45.991147],[6.5001,46.429673],[6.022609,46.27299],[6.037389,46.725779],[6.768714,47.287708],[6.736571,47.541801],[7.192202,47.449766],[7.466759,47.620582],[8.317301,47.61358],[8.522612,47.830828],[9.594226,47.525058]]]},"id":"CHE"},
{"type":"Feature","properties":{"name":"Chile"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-68.63401,-52.63637],[-68.63335,-54.8695],[-67.56244,-54.87001],[-66.95992,-54.89681],[-67.29103,-55.30124],[-68.14863,-55.61183],[-68.639991,-55.580018],[-69.2321,-55.49906],[-69.95809,-55.19843],[-71.00568,-55.05383],[-72.2639,-54.49514],[-73.2852,-53.95752],[-74.66253,-52.83749],[-73.8381,-53.04743],[-72.43418,-53.7154],[-71.10773,-54.07433],[-70.59178,-53.61583],[-70.26748,-52.93123],[-69.34565,-52.5183],[-68.63401,-52.63637]]],[[[-68.219913,-21.494347],[-67.82818,-22.872919],[-67.106674,-22.735925],[-66.985234,-22.986349],[-67.328443,-24.025303],[-68.417653,-24.518555],[-68.386001,-26.185016],[-68.5948,-26.506909],[-68.295542,-26.89934],[-69.001235,-27.521214],[-69.65613,-28.459141],[-70.01355,-29.367923],[-69.919008,-30.336339],[-70.535069,-31.36501],[-70.074399,-33.09121],[-69.814777,-33.273886],[-69.817309,-34.193571],[-70.388049,-35.169688],[-70.364769,-36.005089],[-71.121881,-36.658124],[-71.118625,-37.576827],[-70.814664,-38.552995],[-71.413517,-38.916022],[-71.680761,-39.808164],[-71.915734,-40.832339],[-71.746804,-42.051386],[-72.148898,-42.254888],[-71.915424,-43.408565],[-71.464056,-43.787611],[-71.793623,-44.207172],[-71.329801,-44.407522],[-71.222779,-44.784243],[-71.659316,-44.973689],[-71.552009,-45.560733],[-71.917258,-46.884838],[-72.447355,-47.738533],[-72.331161,-48.244238],[-72.648247,-48.878618],[-73.415436,-49.318436],[-73.328051,-50.378785],[-72.975747,-50.74145],[-72.309974,-50.67701],[-72.329404,-51.425956],[-71.914804,-52.009022],[-69.498362,-52.142761],[-68.571545,-52.299444],[-69.461284,-52.291951],[-69.94278,-52.537931],[-70.845102,-52.899201],[-71.006332,-53.833252],[-71.429795,-53.856455],[-72.557943,-53.53141],[-73.702757,-52.835069],[-73.702757,-52.83507],[-74.946763,-52.262754],[-75.260026,-51.629355],[-74.976632,-51.043396],[-75.479754,-50.378372],[-75.608015,-48.673773],[-75.18277,-47.711919],[-74.126581,-46.939253],[-75.644395,-46.647643],[-74.692154,-45.763976],[-74.351709,-44.103044],[-73.240356,-44.454961],[-72.717804,-42.383356],[-73.3889,-42.117532],[-73.701336,-43.365776],[-74.331943,-43.224958],[-74.017957,-41.794813],[-73.677099,-39.942213],[-73.217593,-39.258689],[-73.505559,-38.282883],[-73.588061,-37.156285],[-73.166717,-37.12378],[-72.553137,-35.50884],[-71.861732,-33.909093],[-71.43845,-32.418899],[-71.668721,-30.920645],[-71.370083,-30.095682],[-71.489894,-28.861442],[-70.905124,-27.64038],[-70.724954,-25.705924],[-70.403966,-23.628997],[-70.091246,-21.393319],[-70.16442,-19.756468],[-70.372572,-18.347975],[-69.858444,-18.092694],[-69.590424,-17.580012],[-69.100247,-18.260125],[-68.966818,-18.981683],[-68.442225,-19.405068],[-68.757167,-20.372658],[-68.219913,-21.494347]]]]},"id":"CHL"},
{"type":"Feature","properties":{"name":"China"},"geometry":{"type":"MultiPolygon","coordinates":[[[[110.339188,18.678395],[109.47521,18.197701],[108.655208,18.507682],[108.626217,19.367888],[109.119056,19.821039],[110.211599,20.101254],[110.786551,20.077534],[111.010051,19.69593],[110.570647,19.255879],[110.339188,18.678395]]],[[[127.657407,49.76027],[129.397818,49.4406],[130.582293,48.729687],[130.987282,47.790132],[132.506672,47.78897],[133.373596,48.183442],[135.026311,48.47823],[134.500814,47.57844],[134.112362,47.212467],[133.769644,46.116927],[133.097127,45.144066],[131.883454,45.321162],[131.025212,44.967953],[131.288555,44.11152],[131.144688,42.92999],[130.633866,42.903015],[130.640016,42.395009],[129.994267,42.985387],[129.596669,42.424982],[128.052215,41.994285],[128.208433,41.466772],[127.343783,41.503152],[126.869083,41.816569],[126.182045,41.107336],[125.079942,40.569824],[124.265625,39.928493],[122.86757,39.637788],[122.131388,39.170452],[121.054554,38.897471],[121.585995,39.360854],[121.376757,39.750261],[122.168595,40.422443],[121.640359,40.94639],[120.768629,40.593388],[119.639602,39.898056],[119.023464,39.252333],[118.042749,39.204274],[117.532702,38.737636],[118.059699,38.061476],[118.87815,37.897325],[118.911636,37.448464],[119.702802,37.156389],[120.823457,37.870428],[121.711259,37.481123],[122.357937,37.454484],[122.519995,36.930614],[121.104164,36.651329],[120.637009,36.11144],[119.664562,35.609791],[119.151208,34.909859],[120.227525,34.360332],[120.620369,33.376723],[121.229014,32.460319],[121.908146,31.692174],[121.891919,30.949352],[121.264257,30.676267],[121.503519,30.142915],[122.092114,29.83252],[121.938428,29.018022],[121.684439,28.225513],[121.125661,28.135673],[120.395473,27.053207],[119.585497,25.740781],[118.656871,24.547391],[117.281606,23.624501],[115.890735,22.782873],[114.763827,22.668074],[114.152547,22.22376],[113.80678,22.54834],[113.241078,22.051367],[111.843592,21.550494],[110.785466,21.397144],[110.444039,20.341033],[109.889861,20.282457],[109.627655,21.008227],[109.864488,21.395051],[108.522813,21.715212],[108.05018,21.55238],[107.04342,21.811899],[106.567273,22.218205],[106.725403,22.794268],[105.811247,22.976892],[105.329209,23.352063],[104.476858,22.81915],[103.504515,22.703757],[102.706992,22.708795],[102.170436,22.464753],[101.652018,22.318199],[101.80312,21.174367],[101.270026,21.201652],[101.180005,21.436573],[101.150033,21.849984],[100.416538,21.558839],[99.983489,21.742937],[99.240899,22.118314],[99.531992,22.949039],[98.898749,23.142722],[98.660262,24.063286],[97.60472,23.897405],[97.724609,25.083637],[98.671838,25.918703],[98.712094,26.743536],[98.68269,27.508812],[98.246231,27.747221],[97.911988,28.335945],[97.327114,28.261583],[96.248833,28.411031],[96.586591,28.83098],[96.117679,29.452802],[95.404802,29.031717],[94.56599,29.277438],[93.413348,28.640629],[92.503119,27.896876],[91.696657,27.771742],[91.258854,28.040614],[90.730514,28.064954],[90.015829,28.296439],[89.47581,28.042759],[88.814248,27.299316],[88.730326,28.086865],[88.120441,27.876542],[86.954517,27.974262],[85.82332,28.203576],[85.011638,28.642774],[84.23458,28.839894],[83.898993,29.320226],[83.337115,29.463732],[82.327513,30.115268],[81.525804,30.422717],[81.111256,30.183481],[79.721367,30.882715],[78.738894,31.515906],[78.458446,32.618164],[79.176129,32.48378],[79.208892,32.994395],[78.811086,33.506198],[78.912269,34.321936],[77.837451,35.49401],[76.192848,35.898403],[75.896897,36.666806],[75.158028,37.133031],[74.980002,37.41999],[74.829986,37.990007],[74.864816,38.378846],[74.257514,38.606507],[73.928852,38.505815],[73.675379,39.431237],[73.960013,39.660008],[73.822244,39.893973],[74.776862,40.366425],[75.467828,40.562072],[76.526368,40.427946],[76.904484,41.066486],[78.187197,41.185316],[78.543661,41.582243],[80.11943,42.123941],[80.25999,42.349999],[80.18015,42.920068],[80.866206,43.180362],[79.966106,44.917517],[81.947071,45.317027],[82.458926,45.53965],[83.180484,47.330031],[85.16429,47.000956],[85.720484,47.452969],[85.768233,48.455751],[86.598776,48.549182],[87.35997,49.214981],[87.751264,49.297198],[88.013832,48.599463],[88.854298,48.069082],[90.280826,47.693549],[90.970809,46.888146],[90.585768,45.719716],[90.94554,45.286073],[92.133891,45.115076],[93.480734,44.975472],[94.688929,44.352332],[95.306875,44.241331],[95.762455,43.319449],[96.349396,42.725635],[97.451757,42.74889],[99.515817,42.524691],[100.845866,42.663804],[101.83304,42.514873],[103.312278,41.907468],[104.522282,41.908347],[104.964994,41.59741],[106.129316,42.134328],[107.744773,42.481516],[109.243596,42.519446],[110.412103,42.871234],[111.129682,43.406834],[111.829588,43.743118],[111.667737,44.073176],[111.348377,44.457442],[111.873306,45.102079],[112.436062,45.011646],[113.463907,44.808893],[114.460332,45.339817],[115.985096,45.727235],[116.717868,46.388202],[117.421701,46.672733],[118.874326,46.805412],[119.66327,46.69268],[119.772824,47.048059],[118.866574,47.74706],[118.064143,48.06673],[117.295507,47.6977Line truncated
{"type":"Feature","properties":{"name":"Ivory Coast"},"geometry":{"type":"Polygon","coordinates":[[[-2.856125,4.994476],[-3.311084,4.984296],[-4.00882,5.179813],[-4.649917,5.168264],[-5.834496,4.993701],[-6.528769,4.705088],[-7.518941,4.338288],[-7.712159,4.364566],[-7.635368,5.188159],[-7.539715,5.313345],[-7.570153,5.707352],[-7.993693,6.12619],[-8.311348,6.193033],[-8.60288,6.467564],[-8.385452,6.911801],[-8.485446,7.395208],[-8.439298,7.686043],[-8.280703,7.68718],[-8.221792,8.123329],[-8.299049,8.316444],[-8.203499,8.455453],[-7.8321,8.575704],[-8.079114,9.376224],[-8.309616,9.789532],[-8.229337,10.12902],[-8.029944,10.206535],[-7.89959,10.297382],[-7.622759,10.147236],[-6.850507,10.138994],[-6.666461,10.430811],[-6.493965,10.411303],[-6.205223,10.524061],[-6.050452,10.096361],[-5.816926,10.222555],[-5.404342,10.370737],[-4.954653,10.152714],[-4.779884,9.821985],[-4.330247,9.610835],[-3.980449,9.862344],[-3.511899,9.900326],[-2.827496,9.642461],[-2.56219,8.219628],[-2.983585,7.379705],[-3.24437,6.250472],[-2.810701,5.389051],[-2.856125,4.994476]]]},"id":"CIV"},
{"type":"Feature","properties":{"name":"Cameroon"},"geometry":{"type":"Polygon","coordinates":[[[13.075822,2.267097],[12.951334,2.321616],[12.35938,2.192812],[11.751665,2.326758],[11.276449,2.261051],[9.649158,2.283866],[9.795196,3.073404],[9.404367,3.734527],[8.948116,3.904129],[8.744924,4.352215],[8.488816,4.495617],[8.500288,4.771983],[8.757533,5.479666],[9.233163,6.444491],[9.522706,6.453482],[10.118277,7.03877],[10.497375,7.055358],[11.058788,6.644427],[11.745774,6.981383],[11.839309,7.397042],[12.063946,7.799808],[12.218872,8.305824],[12.753672,8.717763],[12.955468,9.417772],[13.1676,9.640626],[13.308676,10.160362],[13.57295,10.798566],[14.415379,11.572369],[14.468192,11.904752],[14.577178,12.085361],[14.181336,12.483657],[14.213531,12.802035],[14.495787,12.859396],[14.893386,12.219048],[14.960152,11.555574],[14.923565,10.891325],[15.467873,9.982337],[14.909354,9.992129],[14.627201,9.920919],[14.171466,10.021378],[13.954218,9.549495],[14.544467,8.965861],[14.979996,8.796104],[15.120866,8.38215],[15.436092,7.692812],[15.27946,7.421925],[14.776545,6.408498],[14.53656,6.226959],[14.459407,5.451761],[14.558936,5.030598],[14.478372,4.732605],[14.950953,4.210389],[15.03622,3.851367],[15.405396,3.335301],[15.862732,3.013537],[15.907381,2.557389],[16.012852,2.26764],[15.940919,1.727673],[15.146342,1.964015],[14.337813,2.227875],[13.075822,2.267097]]]},"id":"CMR"},
{"type":"Feature","properties":{"name":"Democratic Republic of the Congo"},"geometry":{"type":"Polygon","coordinates":[[[30.83386,3.509166],[30.773347,2.339883],[31.174149,2.204465],[30.85267,1.849396],[30.468508,1.583805],[30.086154,1.062313],[29.875779,0.59738],[29.819503,-0.20531],[29.587838,-0.587406],[29.579466,-1.341313],[29.291887,-1.620056],[29.254835,-2.21511],[29.117479,-2.292211],[29.024926,-2.839258],[29.276384,-3.293907],[29.339998,-4.499983],[29.519987,-5.419979],[29.419993,-5.939999],[29.620032,-6.520015],[30.199997,-7.079981],[30.740015,-8.340007],[30.346086,-8.238257],[29.002912,-8.407032],[28.734867,-8.526559],[28.449871,-9.164918],[28.673682,-9.605925],[28.49607,-10.789884],[28.372253,-11.793647],[28.642417,-11.971569],[29.341548,-12.360744],[29.616001,-12.178895],[29.699614,-13.257227],[28.934286,-13.248958],[28.523562,-12.698604],[28.155109,-12.272481],[27.388799,-12.132747],[27.16442,-11.608748],[26.553088,-11.92444],[25.75231,-11.784965],[25.418118,-11.330936],[24.78317,-11.238694],[24.314516,-11.262826],[24.257155,-10.951993],[23.912215,-10.926826],[23.456791,-10.867863],[22.837345,-11.017622],[22.402798,-10.993075],[22.155268,-11.084801],[22.208753,-9.894796],[21.875182,-9.523708],[21.801801,-8.908707],[21.949131,-8.305901],[21.746456,-7.920085],[21.728111,-7.290872],[20.514748,-7.299606],[20.601823,-6.939318],[20.091622,-6.94309],[20.037723,-7.116361],[19.417502,-7.155429],[19.166613,-7.738184],[19.016752,-7.988246],[18.464176,-7.847014],[18.134222,-7.987678],[17.47297,-8.068551],[17.089996,-7.545689],[16.860191,-7.222298],[16.57318,-6.622645],[16.326528,-5.87747],[13.375597,-5.864241],[13.024869,-5.984389],[12.735171,-5.965682],[12.322432,-6.100092],[12.182337,-5.789931],[12.436688,-5.684304],[12.468004,-5.248362],[12.631612,-4.991271],[12.995517,-4.781103],[13.25824,-4.882957],[13.600235,-4.500138],[14.144956,-4.510009],[14.209035,-4.793092],[14.582604,-4.970239],[15.170992,-4.343507],[15.75354,-3.855165],[16.00629,-3.535133],[15.972803,-2.712392],[16.407092,-1.740927],[16.865307,-1.225816],[17.523716,-0.74383],[17.638645,-0.424832],[17.663553,-0.058084],[17.82654,0.288923],[17.774192,0.855659],[17.898835,1.741832],[18.094276,2.365722],[18.393792,2.900443],[18.453065,3.504386],[18.542982,4.201785],[18.932312,4.709506],[19.467784,5.031528],[20.290679,4.691678],[20.927591,4.322786],[21.659123,4.224342],[22.405124,4.02916],[22.704124,4.633051],[22.84148,4.710126],[23.297214,4.609693],[24.410531,5.108784],[24.805029,4.897247],[25.128833,4.927245],[25.278798,5.170408],[25.650455,5.256088],[26.402761,5.150875],[27.044065,5.127853],[27.374226,5.233944],[27.979977,4.408413],[28.428994,4.287155],[28.696678,4.455077],[29.159078,4.389267],[29.715995,4.600805],[29.9535,4.173699],[30.83386,3.509166]]]},"id":"COD"},
{"type":"Feature","properties":{"name":"Republic of the Congo"},"geometry":{"type":"Polygon","coordinates":[[[12.995517,-4.781103],[12.62076,-4.438023],[12.318608,-4.60623],[11.914963,-5.037987],[11.093773,-3.978827],[11.855122,-3.426871],[11.478039,-2.765619],[11.820964,-2.514161],[12.495703,-2.391688],[12.575284,-1.948511],[13.109619,-2.42874],[13.992407,-2.470805],[14.29921,-1.998276],[14.425456,-1.333407],[14.316418,-0.552627],[13.843321,0.038758],[14.276266,1.19693],[14.026669,1.395677],[13.282631,1.314184],[13.003114,1.830896],[13.075822,2.267097],[14.337813,2.227875],[15.146342,1.964015],[15.940919,1.727673],[16.012852,2.26764],[16.537058,3.198255],[17.133042,3.728197],[17.8099,3.560196],[18.453065,3.504386],[18.393792,2.900443],[18.094276,2.365722],[17.898835,1.741832],[17.774192,0.855659],[17.82654,0.288923],[17.663553,-0.058084],[17.638645,-0.424832],[17.523716,-0.74383],[16.865307,-1.225816],[16.407092,-1.740927],[15.972803,-2.712392],[16.00629,-3.535133],[15.75354,-3.855165],[15.170992,-4.343507],[14.582604,-4.970239],[14.209035,-4.793092],[14.144956,-4.510009],[13.600235,-4.500138],[13.25824,-4.882957],[12.995517,-4.781103]]]},"id":"COG"},
{"type":"Feature","properties":{"name":"Colombia"},"geometry":{"type":"Polygon","coordinates":[[[-75.373223,-0.152032],[-75.801466,0.084801],[-76.292314,0.416047],[-76.57638,0.256936],[-77.424984,0.395687],[-77.668613,0.825893],[-77.855061,0.809925],[-78.855259,1.380924],[-78.990935,1.69137],[-78.617831,1.766404],[-78.662118,2.267355],[-78.42761,2.629556],[-77.931543,2.696606],[-77.510431,3.325017],[-77.12769,3.849636],[-77.496272,4.087606],[-77.307601,4.667984],[-77.533221,5.582812],[-77.318815,5.845354],[-77.476661,6.691116],[-77.881571,7.223771],[-77.753414,7.70984],[-77.431108,7.638061],[-77.242566,7.935278],[-77.474723,8.524286],[-77.353361,8.670505],[-76.836674,8.638749],[-76.086384,9.336821],[-75.6746,9.443248],[-75.664704,9.774003],[-75.480426,10.61899],[-74.906895,11.083045],[-74.276753,11.102036],[-74.197223,11.310473],[-73.414764,11.227015],[-72.627835,11.731972],[-72.238195,11.95555],[-71.75409,12.437303],[-71.399822,12.376041],[-71.137461,12.112982],[-71.331584,11.776284],[-71.973922,11.608672],[-72.227575,11.108702],[-72.614658,10.821975],[-72.905286,10.450344],[-73.027604,9.73677],[-73.304952,9.152],[-72.78873,9.085027],[-72.660495,8.625288],[-72.439862,8.405275],[-72.360901,8.002638],[-72.479679,7.632506],[-72.444487,7.423785],[-72.198352,7.340431],[-71.960176,6.991615],[-70.674234,7.087785],[-70.093313,6.960376],[-69.38948,6.099861],[-68.985319,6.206805],[-68.265052,6.153268],[-67.695087,6.267318],[-67.34144,6.095468],[-67.521532,5.55687],[-67.744697,5.221129],[-67.823012,4.503937],[-67.621836,3.839482],[-67.337564,3.542342],[-67.303173,3.318454],[-67.809938,2.820655],[-67.447092,2.600281],[-67.181294,2.250638],[-66.876326,1.253361],[-67.065048,1.130112],[-67.259998,1.719999],[-67.53781,2.037163],[-67.868565,1.692455],[-69.816973,1.714805],[-69.804597,1.089081],[-69.218638,0.985677],[-69.252434,0.602651],[-69.452396,0.706159],[-70.015566,0.541414],[-70.020656,-0.185156],[-69.577065,-0.549992],[-69.420486,-1.122619],[-69.444102,-1.556287],[-69.893635,-4.298187],[-70.394044,-3.766591],[-70.692682,-3.742872],[-70.047709,-2.725156],[-70.813476,-2.256865],[-71.413646,-2.342802],[-71.774761,-2.16979],[-72.325787,-2.434218],[-73.070392,-2.308954],[-73.659504,-1.260491],[-74.122395,-1.002833],[-74.441601,-0.53082],[-75.106625,-0.057205],[-75.373223,-0.152032]]]},"id":"COL"},
{"type":"Feature","properties":{"name":"Costa Rica"},"geometry":{"type":"Polygon","coordinates":[[[-82.965783,8.225028],[-83.508437,8.446927],[-83.711474,8.656836],[-83.596313,8.830443],[-83.632642,9.051386],[-83.909886,9.290803],[-84.303402,9.487354],[-84.647644,9.615537],[-84.713351,9.908052],[-84.97566,10.086723],[-84.911375,9.795992],[-85.110923,9.55704],[-85.339488,9.834542],[-85.660787,9.933347],[-85.797445,10.134886],[-85.791709,10.439337],[-85.659314,10.754331],[-85.941725,10.895278],[-85.71254,11.088445],[-85.561852,11.217119],[-84.903003,10.952303],[-84.673069,11.082657],[-84.355931,10.999226],[-84.190179,10.79345],[-83.895054,10.726839],[-83.655612,10.938764],[-83.40232,10.395438],[-83.015677,9.992982],[-82.546196,9.566135],[-82.932891,9.476812],[-82.927155,9.07433],[-82.719183,8.925709],[-82.868657,8.807266],[-82.829771,8.626295],[-82.913176,8.423517],[-82.965783,8.225028]]]},"id":"CRI"},
{"type":"Feature","properties":{"name":"Cuba"},"geometry":{"type":"Polygon","coordinates":[[[-82.268151,23.188611],[-81.404457,23.117271],[-80.618769,23.10598],[-79.679524,22.765303],[-79.281486,22.399202],[-78.347434,22.512166],[-77.993296,22.277194],[-77.146422,21.657851],[-76.523825,21.20682],[-76.19462,21.220565],[-75.598222,21.016624],[-75.67106,20.735091],[-74.933896,20.693905],[-74.178025,20.284628],[-74.296648,20.050379],[-74.961595,19.923435],[-75.63468,19.873774],[-76.323656,19.952891],[-77.755481,19.855481],[-77.085108,20.413354],[-77.492655,20.673105],[-78.137292,20.739949],[-78.482827,21.028613],[-78.719867,21.598114],[-79.285,21.559175],[-80.217475,21.827324],[-80.517535,22.037079],[-81.820943,22.192057],[-82.169992,22.387109],[-81.795002,22.636965],[-82.775898,22.68815],[-83.494459,22.168518],[-83.9088,22.154565],[-84.052151,21.910575],[-84.54703,21.801228],[-84.974911,21.896028],[-84.447062,22.20495],[-84.230357,22.565755],[-83.77824,22.788118],[-83.267548,22.983042],[-82.510436,23.078747],[-82.268151,23.188611]]]},"id":"CUB"},
{"type":"Feature","properties":{"name":"Northern Cyprus"},"geometry":{"type":"Polygon","coordinates":[[[32.73178,35.140026],[32.802474,35.145504],[32.946961,35.386703],[33.667227,35.373216],[34.576474,35.671596],[33.900804,35.245756],[33.973617,35.058506],[33.86644,35.093595],[33.675392,35.017863],[33.525685,35.038688],[33.475817,35.000345],[33.455922,35.101424],[33.383833,35.162712],[33.190977,35.173125],[32.919572,35.087833],[32.73178,35.140026]]]},"id":"-99"},
{"type":"Feature","properties":{"name":"Cyprus"},"geometry":{"type":"Polygon","coordinates":[[[33.973617,35.058506],[34.004881,34.978098],[32.979827,34.571869],[32.490296,34.701655],[32.256667,35.103232],[32.73178,35.140026],[32.919572,35.087833],[33.190977,35.173125],[33.383833,35.162712],[33.455922,35.101424],[33.475817,35.000345],[33.525685,35.038688],[33.675392,35.017863],[33.86644,35.093595],[33.973617,35.058506]]]},"id":"CYP"},
{"type":"Feature","properties":{"name":"Czech Republic"},"geometry":{"type":"Polygon","coordinates":[[[16.960288,48.596982],[16.499283,48.785808],[16.029647,48.733899],[15.253416,49.039074],[14.901447,48.964402],[14.338898,48.555305],[13.595946,48.877172],[13.031329,49.307068],[12.521024,49.547415],[12.415191,49.969121],[12.240111,50.266338],[12.966837,50.484076],[13.338132,50.733234],[14.056228,50.926918],[14.307013,51.117268],[14.570718,51.002339],[15.016996,51.106674],[15.490972,50.78473],[16.238627,50.697733],[16.176253,50.422607],[16.719476,50.215747],[16.868769,50.473974],[17.554567,50.362146],[17.649445,50.049038],[18.392914,49.988629],[18.853144,49.49623],[18.554971,49.495015],[18.399994,49.315001],[18.170498,49.271515],[18.104973,49.043983],[17.913512,48.996493],[17.886485,48.903475],[17.545007,48.800019],[17.101985,48.816969],[16.960288,48.596982]]]},"id":"CZE"},
{"type":"Feature","properties":{"name":"Germany"},"geometry":{"type":"Polygon","coordinates":[[[9.921906,54.983104],[9.93958,54.596642],[10.950112,54.363607],[10.939467,54.008693],[11.956252,54.196486],[12.51844,54.470371],[13.647467,54.075511],[14.119686,53.757029],[14.353315,53.248171],[14.074521,52.981263],[14.4376,52.62485],[14.685026,52.089947],[14.607098,51.745188],[15.016996,51.106674],[14.570718,51.002339],[14.307013,51.117268],[14.056228,50.926918],[13.338132,50.733234],[12.966837,50.484076],[12.240111,50.266338],[12.415191,49.969121],[12.521024,49.547415],[13.031329,49.307068],[13.595946,48.877172],[13.243357,48.416115],[12.884103,48.289146],[13.025851,47.637584],[12.932627,47.467646],[12.62076,47.672388],[12.141357,47.703083],[11.426414,47.523766],[10.544504,47.566399],[10.402084,47.302488],[9.896068,47.580197],[9.594226,47.525058],[8.522612,47.830828],[8.317301,47.61358],[7.466759,47.620582],[7.593676,48.333019],[8.099279,49.017784],[6.65823,49.201958],[6.18632,49.463803],[6.242751,49.902226],[6.043073,50.128052],[6.156658,50.803721],[5.988658,51.851616],[6.589397,51.852029],[6.84287,52.22844],[7.092053,53.144043],[6.90514,53.482162],[7.100425,53.693932],[7.936239,53.748296],[8.121706,53.527792],[8.800734,54.020786],[8.572118,54.395646],[8.526229,54.962744],[9.282049,54.830865],[9.921906,54.983104]]]},"id":"DEU"},
{"type":"Feature","properties":{"name":"Djibouti"},"geometry":{"type":"Polygon","coordinates":[[[43.081226,12.699639],[43.317852,12.390148],[43.286381,11.974928],[42.715874,11.735641],[43.145305,11.46204],[42.776852,10.926879],[42.55493,11.10511],[42.31414,11.0342],[41.75557,11.05091],[41.73959,11.35511],[41.66176,11.6312],[42,12.1],[42.35156,12.54223],[42.779642,12.455416],[43.081226,12.699639]]]},"id":"DJI"},
{"type":"Feature","properties":{"name":"Denmark"},"geometry":{"type":"MultiPolygon","coordinates":[[[[12.690006,55.609991],[12.089991,54.800015],[11.043543,55.364864],[10.903914,55.779955],[12.370904,56.111407],[12.690006,55.609991]]],[[[10.912182,56.458621],[10.667804,56.081383],[10.369993,56.190007],[9.649985,55.469999],[9.921906,54.983104],[9.282049,54.830865],[8.526229,54.962744],[8.120311,55.517723],[8.089977,56.540012],[8.256582,56.809969],[8.543438,57.110003],[9.424469,57.172066],[9.775559,57.447941],[10.580006,57.730017],[10.546106,57.215733],[10.25,56.890016],[10.369993,56.609982],[10.912182,56.458621]]]]},"id":"DNK"},
{"type":"Feature","properties":{"name":"Dominican Republic"},"geometry":{"type":"Polygon","coordinates":[[[-71.712361,19.714456],[-71.587304,19.884911],[-70.806706,19.880286],[-70.214365,19.622885],[-69.950815,19.648],[-69.76925,19.293267],[-69.222126,19.313214],[-69.254346,19.015196],[-68.809412,18.979074],[-68.317943,18.612198],[-68.689316,18.205142],[-69.164946,18.422648],[-69.623988,18.380713],[-69.952934,18.428307],[-70.133233,18.245915],[-70.517137,18.184291],[-70.669298,18.426886],[-70.99995,18.283329],[-71.40021,17.598564],[-71.657662,17.757573],[-71.708305,18.044997],[-71.687738,18.31666],[-71.945112,18.6169],[-71.701303,18.785417],[-71.624873,19.169838],[-71.712361,19.714456]]]},"id":"DOM"},
{"type":"Feature","properties":{"name":"Algeria"},"geometry":{"type":"Polygon","coordinates":[[[11.999506,23.471668],[8.572893,21.565661],[5.677566,19.601207],[4.267419,19.155265],[3.158133,19.057364],[3.146661,19.693579],[2.683588,19.85623],[2.060991,20.142233],[1.823228,20.610809],[-1.550055,22.792666],[-4.923337,24.974574],[-8.6844,27.395744],[-8.665124,27.589479],[-8.66559,27.656426],[-8.674116,28.841289],[-7.059228,29.579228],[-6.060632,29.7317],[-5.242129,30.000443],[-4.859646,30.501188],[-3.690441,30.896952],[-3.647498,31.637294],[-3.06898,31.724498],[-2.616605,32.094346],[-1.307899,32.262889],[-1.124551,32.651522],[-1.388049,32.864015],[-1.733455,33.919713],[-1.792986,34.527919],[-2.169914,35.168396],[-1.208603,35.714849],[-0.127454,35.888662],[0.503877,36.301273],[1.466919,36.605647],[3.161699,36.783905],[4.815758,36.865037],[5.32012,36.716519],[6.26182,37.110655],[7.330385,37.118381],[7.737078,36.885708],[8.420964,36.946427],[8.217824,36.433177],[8.376368,35.479876],[8.140981,34.655146],[7.524482,34.097376],[7.612642,33.344115],[8.430473,32.748337],[8.439103,32.506285],[9.055603,32.102692],[9.48214,30.307556],[9.805634,29.424638],[9.859998,28.95999],[9.683885,28.144174],[9.756128,27.688259],[9.629056,27.140953],[9.716286,26.512206],[9.319411,26.094325],[9.910693,25.365455],[9.948261,24.936954],[10.303847,24.379313],[10.771364,24.562532],[11.560669,24.097909],[11.999506,23.471668]]]},"id":"DZA"},
{"type":"Feature","properties":{"name":"Ecuador"},"geometry":{"type":"Polygon","coordinates":[[[-80.302561,-3.404856],[-79.770293,-2.657512],[-79.986559,-2.220794],[-80.368784,-2.685159],[-80.967765,-2.246943],[-80.764806,-1.965048],[-80.933659,-1.057455],[-80.58337,-0.906663],[-80.399325,-0.283703],[-80.020898,0.36034],[-80.09061,0.768429],[-79.542762,0.982938],[-78.855259,1.380924],[-77.855061,0.809925],[-77.668613,0.825893],[-77.424984,0.395687],[-76.57638,0.256936],[-76.292314,0.416047],[-75.801466,0.084801],[-75.373223,-0.152032],[-75.233723,-0.911417],[-75.544996,-1.56161],[-76.635394,-2.608678],[-77.837905,-3.003021],[-78.450684,-3.873097],[-78.639897,-4.547784],[-79.205289,-4.959129],[-79.624979,-4.454198],[-80.028908,-4.346091],[-80.442242,-4.425724],[-80.469295,-4.059287],[-80.184015,-3.821162],[-80.302561,-3.404856]]]},"id":"ECU"},
{"type":"Feature","properties":{"name":"Egypt"},"geometry":{"type":"Polygon","coordinates":[[[34.9226,29.50133],[34.64174,29.09942],[34.42655,28.34399],[34.15451,27.8233],[33.92136,27.6487],[33.58811,27.97136],[33.13676,28.41765],[32.42323,29.85108],[32.32046,29.76043],[32.73482,28.70523],[33.34876,27.69989],[34.10455,26.14227],[34.47387,25.59856],[34.79507,25.03375],[35.69241,23.92671],[35.49372,23.75237],[35.52598,23.10244],[36.69069,22.20485],[36.86623,22],[32.9,22],[29.02,22],[25,22],[25,25.6825],[25,29.238655],[24.70007,30.04419],[24.95762,30.6616],[24.80287,31.08929],[25.16482,31.56915],[26.49533,31.58568],[27.45762,31.32126],[28.45048,31.02577],[28.91353,30.87005],[29.68342,31.18686],[30.09503,31.4734],[30.97693,31.55586],[31.68796,31.4296],[31.96041,30.9336],[32.19247,31.26034],[32.99392,31.02407],[33.7734,30.96746],[34.26544,31.21936],[34.9226,29.50133]]]},"id":"EGY"},
{"type":"Feature","properties":{"name":"Eritrea"},"geometry":{"type":"Polygon","coordinates":[[[42.35156,12.54223],[42.00975,12.86582],[41.59856,13.45209],[41.155194,13.77332],[40.8966,14.11864],[40.026219,14.519579],[39.34061,14.53155],[39.0994,14.74064],[38.51295,14.50547],[37.90607,14.95943],[37.59377,14.2131],[36.42951,14.42211],[36.323189,14.822481],[36.75386,16.291874],[36.85253,16.95655],[37.16747,17.26314],[37.904,17.42754],[38.41009,17.998307],[38.990623,16.840626],[39.26611,15.922723],[39.814294,15.435647],[41.179275,14.49108],[41.734952,13.921037],[42.276831,13.343992],[42.589576,13.000421],[43.081226,12.699639],[42.779642,12.455416],[42.35156,12.54223]]]},"id":"ERI"},
{"type":"Feature","properties":{"name":"Spain"},"geometry":{"type":"Polygon","coordinates":[[[-9.034818,41.880571],[-8.984433,42.592775],[-9.392884,43.026625],[-7.97819,43.748338],[-6.754492,43.567909],[-5.411886,43.57424],[-4.347843,43.403449],[-3.517532,43.455901],[-1.901351,43.422802],[-1.502771,43.034014],[0.338047,42.579546],[0.701591,42.795734],[1.826793,42.343385],[2.985999,42.473015],[3.039484,41.89212],[2.091842,41.226089],[0.810525,41.014732],[0.721331,40.678318],[0.106692,40.123934],[-0.278711,39.309978],[0.111291,38.738514],[-0.467124,38.292366],[-0.683389,37.642354],[-1.438382,37.443064],[-2.146453,36.674144],[-3.415781,36.6589],[-4.368901,36.677839],[-4.995219,36.324708],[-5.37716,35.94685],[-5.866432,36.029817],[-6.236694,36.367677],[-6.520191,36.942913],[-7.453726,37.097788],[-7.537105,37.428904],[-7.166508,37.803894],[-7.029281,38.075764],[-7.374092,38.373059],[-7.098037,39.030073],[-7.498632,39.629571],[-7.066592,39.711892],[-7.026413,40.184524],[-6.86402,40.330872],[-6.851127,41.111083],[-6.389088,41.381815],[-6.668606,41.883387],[-7.251309,41.918346],[-7.422513,41.792075],[-8.013175,41.790886],[-8.263857,42.280469],[-8.671946,42.134689],[-9.034818,41.880571]]]},"id":"ESP"},
{"type":"Feature","properties":{"name":"Estonia"},"geometry":{"type":"Polygon","coordinates":[[[24.312863,57.793424],[24.428928,58.383413],[24.061198,58.257375],[23.42656,58.612753],[23.339795,59.18724],[24.604214,59.465854],[25.864189,59.61109],[26.949136,59.445803],[27.981114,59.475388],[28.131699,59.300825],[27.420166,58.724581],[27.716686,57.791899],[27.288185,57.474528],[26.463532,57.476389],[25.60281,57.847529],[25.164594,57.970157],[24.312863,57.793424]]]},"id":"EST"},
{"type":"Feature","properties":{"name":"Ethiopia"},"geometry":{"type":"Polygon","coordinates":[[[37.90607,14.95943],[38.51295,14.50547],[39.0994,14.74064],[39.34061,14.53155],[40.02625,14.51959],[40.8966,14.11864],[41.1552,13.77333],[41.59856,13.45209],[42.00975,12.86582],[42.35156,12.54223],[42,12.1],[41.66176,11.6312],[41.73959,11.35511],[41.75557,11.05091],[42.31414,11.0342],[42.55493,11.10511],[42.776852,10.926879],[42.55876,10.57258],[42.92812,10.02194],[43.29699,9.54048],[43.67875,9.18358],[46.94834,7.99688],[47.78942,8.003],[44.9636,5.00162],[43.66087,4.95755],[42.76967,4.25259],[42.12861,4.23413],[41.855083,3.918912],[41.1718,3.91909],[40.76848,4.25702],[39.85494,3.83879],[39.559384,3.42206],[38.89251,3.50074],[38.67114,3.61607],[38.43697,3.58851],[38.120915,3.598605],[36.855093,4.447864],[36.159079,4.447864],[35.817448,4.776966],[35.817448,5.338232],[35.298007,5.506],[34.70702,6.59422],[34.25032,6.82607],[34.0751,7.22595],[33.56829,7.71334],[32.95418,7.78497],[33.2948,8.35458],[33.8255,8.37916],[33.97498,8.68456],[33.96162,9.58358],[34.25745,10.63009],[34.73115,10.91017],[34.83163,11.31896],[35.26049,12.08286],[35.86363,12.57828],[36.27022,13.56333],[36.42951,14.42211],[37.59377,14.2131],[37.90607,14.95943]]]},"id":"ETH"},
{"type":"Feature","properties":{"name":"Finland"},"geometry":{"type":"Polygon","coordinates":[[[28.59193,69.064777],[28.445944,68.364613],[29.977426,67.698297],[29.054589,66.944286],[30.21765,65.80598],[29.54443,64.948672],[30.444685,64.204453],[30.035872,63.552814],[31.516092,62.867687],[31.139991,62.357693],[30.211107,61.780028],[28.069998,60.503517],[26.255173,60.423961],[24.496624,60.057316],[22.869695,59.846373],[22.290764,60.391921],[21.322244,60.72017],[21.544866,61.705329],[21.059211,62.607393],[21.536029,63.189735],[22.442744,63.81781],[24.730512,64.902344],[25.398068,65.111427],[25.294043,65.534346],[23.903379,66.006927],[23.56588,66.396051],[23.539473,67.936009],[21.978535,68.616846],[20.645593,69.106247],[21.244936,69.370443],[22.356238,68.841741],[23.66205,68.891247],[24.735679,68.649557],[25.689213,69.092114],[26.179622,69.825299],[27.732292,70.164193],[29.015573,69.766491],[28.59193,69.064777]]]},"id":"FIN"},
{"type":"Feature","properties":{"name":"Fiji"},"geometry":{"type":"MultiPolygon","coordinates":[[[[178.3736,-17.33992],[178.71806,-17.62846],[178.55271,-18.15059],[177.93266,-18.28799],[177.38146,-18.16432],[177.28504,-17.72465],[177.67087,-17.38114],[178.12557,-17.50481],[178.3736,-17.33992]]],[[[179.364143,-16.801354],[178.725059,-17.012042],[178.596839,-16.63915],[179.096609,-16.433984],[179.413509,-16.379054],[180,-16.067133],[180,-16.555217],[179.364143,-16.801354]]],[[[-179.917369,-16.501783],[-180,-16.555217],[-180,-16.067133],[-179.79332,-16.020882],[-179.917369,-16.501783]]]]},"id":"FJI"},
{"type":"Feature","properties":{"name":"Falkland Islands"},"geometry":{"type":"Polygon","coordinates":[[[-61.2,-51.85],[-60,-51.25],[-59.15,-51.5],[-58.55,-51.1],[-57.75,-51.55],[-58.05,-51.9],[-59.4,-52.2],[-59.85,-51.85],[-60.7,-52.3],[-61.2,-51.85]]]},"id":"FLK"},
{"type":"Feature","properties":{"name":"France"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-52.556425,2.504705],[-52.939657,2.124858],[-53.418465,2.053389],[-53.554839,2.334897],[-53.778521,2.376703],[-54.088063,2.105557],[-54.524754,2.311849],[-54.27123,2.738748],[-54.184284,3.194172],[-54.011504,3.62257],[-54.399542,4.212611],[-54.478633,4.896756],[-53.958045,5.756548],[-53.618453,5.646529],[-52.882141,5.409851],[-51.823343,4.565768],[-51.657797,4.156232],[-52.249338,3.241094],[-52.556425,2.504705]]],[[[9.560016,42.152492],[9.229752,41.380007],[8.775723,41.583612],[8.544213,42.256517],[8.746009,42.628122],[9.390001,43.009985],[9.560016,42.152492]]],[[[3.588184,50.378992],[4.286023,49.907497],[4.799222,49.985373],[5.674052,49.529484],[5.897759,49.442667],[6.18632,49.463803],[6.65823,49.201958],[8.099279,49.017784],[7.593676,48.333019],[7.466759,47.620582],[7.192202,47.449766],[6.736571,47.541801],[6.768714,47.287708],[6.037389,46.725779],[6.022609,46.27299],[6.5001,46.429673],[6.843593,45.991147],[6.802355,45.70858],[7.096652,45.333099],[6.749955,45.028518],[7.007562,44.254767],[7.549596,44.127901],[7.435185,43.693845],[6.529245,43.128892],[4.556963,43.399651],[3.100411,43.075201],[2.985999,42.473015],[1.826793,42.343385],[0.701591,42.795734],[0.338047,42.579546],[-1.502771,43.034014],[-1.901351,43.422802],[-1.384225,44.02261],[-1.193798,46.014918],[-2.225724,47.064363],[-2.963276,47.570327],[-4.491555,47.954954],[-4.59235,48.68416],[-3.295814,48.901692],[-1.616511,48.644421],[-1.933494,49.776342],[-0.989469,49.347376],[1.338761,50.127173],[1.639001,50.946606],[2.513573,51.148506],[2.658422,50.796848],[3.123252,50.780363],[3.588184,50.378992]]]]},"id":"FRA"},
{"type":"Feature","properties":{"name":"Gabon"},"geometry":{"type":"Polygon","coordinates":[[[11.093773,-3.978827],[10.066135,-2.969483],[9.405245,-2.144313],[8.797996,-1.111301],[8.830087,-0.779074],[9.04842,-0.459351],[9.291351,0.268666],[9.492889,1.01012],[9.830284,1.067894],[11.285079,1.057662],[11.276449,2.261051],[11.751665,2.326758],[12.35938,2.192812],[12.951334,2.321616],[13.075822,2.267097],[13.003114,1.830896],[13.282631,1.314184],[14.026669,1.395677],[14.276266,1.19693],[13.843321,0.038758],[14.316418,-0.552627],[14.425456,-1.333407],[14.29921,-1.998276],[13.992407,-2.470805],[13.109619,-2.42874],[12.575284,-1.948511],[12.495703,-2.391688],[11.820964,-2.514161],[11.478039,-2.765619],[11.855122,-3.426871],[11.093773,-3.978827]]]},"id":"GAB"},
{"type":"Feature","properties":{"name":"England"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-5.661949,54.554603],[-6.197885,53.867565],[-6.95373,54.073702],[-7.572168,54.059956],[-7.366031,54.595841],[-7.572168,55.131622],[-6.733847,55.17286],[-5.661949,54.554603]]],[[[-3.005005,58.635],[-4.073828,57.553025],[-3.055002,57.690019],[-1.959281,57.6848],[-2.219988,56.870017],[-3.119003,55.973793],[-2.085009,55.909998],[-2.005676,55.804903],[-1.114991,54.624986],[-0.430485,54.464376],[0.184981,53.325014],[0.469977,52.929999],[1.681531,52.73952],[1.559988,52.099998],[1.050562,51.806761],[1.449865,51.289428],[0.550334,50.765739],[-0.787517,50.774989],[-2.489998,50.500019],[-2.956274,50.69688],[-3.617448,50.228356],[-4.542508,50.341837],[-5.245023,49.96],[-5.776567,50.159678],[-4.30999,51.210001],[-3.414851,51.426009],[-3.422719,51.426848],[-4.984367,51.593466],[-5.267296,51.9914],[-4.222347,52.301356],[-4.770013,52.840005],[-4.579999,53.495004],[-3.093831,53.404547],[-3.09208,53.404441],[-2.945009,53.985],[-3.614701,54.600937],[-3.630005,54.615013],[-4.844169,54.790971],[-5.082527,55.061601],[-4.719112,55.508473],[-5.047981,55.783986],[-5.586398,55.311146],[-5.644999,56.275015],[-6.149981,56.78501],[-5.786825,57.818848],[-5.009999,58.630013],[-4.211495,58.550845],[-3.005005,58.635]]]]},"id":"GBR"},
{"type":"Feature","properties":{"name":"Georgia"},"geometry":{"type":"Polygon","coordinates":[[[41.554084,41.535656],[41.703171,41.962943],[41.45347,42.645123],[40.875469,43.013628],[40.321394,43.128634],[39.955009,43.434998],[40.076965,43.553104],[40.922185,43.382159],[42.394395,43.220308],[43.756017,42.740828],[43.9312,42.554974],[44.537623,42.711993],[45.470279,42.502781],[45.77641,42.092444],[46.404951,41.860675],[46.145432,41.722802],[46.637908,41.181673],[46.501637,41.064445],[45.962601,41.123873],[45.217426,41.411452],[44.97248,41.248129],[43.582746,41.092143],[42.619549,41.583173],[41.554084,41.535656]]]},"id":"GEO"},
{"type":"Feature","properties":{"name":"Ghana"},"geometry":{"type":"Polygon","coordinates":[[[1.060122,5.928837],[-0.507638,5.343473],[-1.063625,5.000548],[-1.964707,4.710462],[-2.856125,4.994476],[-2.810701,5.389051],[-3.24437,6.250472],[-2.983585,7.379705],[-2.56219,8.219628],[-2.827496,9.642461],[-2.963896,10.395335],[-2.940409,10.96269],[-1.203358,11.009819],[-0.761576,10.93693],[-0.438702,11.098341],[0.023803,11.018682],[-0.049785,10.706918],[0.36758,10.191213],[0.365901,9.465004],[0.461192,8.677223],[0.712029,8.312465],[0.490957,7.411744],[0.570384,6.914359],[0.836931,6.279979],[1.060122,5.928837]]]},"id":"GHA"},
{"type":"Feature","properties":{"name":"Guinea"},"geometry":{"type":"Polygon","coordinates":[[[-8.439298,7.686043],[-8.722124,7.711674],[-8.926065,7.309037],[-9.208786,7.313921],[-9.403348,7.526905],[-9.33728,7.928534],[-9.755342,8.541055],[-10.016567,8.428504],[-10.230094,8.406206],[-10.505477,8.348896],[-10.494315,8.715541],[-10.65477,8.977178],[-10.622395,9.26791],[-10.839152,9.688246],[-11.117481,10.045873],[-11.917277,10.046984],[-12.150338,9.858572],[-12.425929,9.835834],[-12.596719,9.620188],[-12.711958,9.342712],[-13.24655,8.903049],[-13.685154,9.494744],[-14.074045,9.886167],[-14.330076,10.01572],[-14.579699,10.214467],[-14.693232,10.656301],[-14.839554,10.876572],[-15.130311,11.040412],[-14.685687,11.527824],[-14.382192,11.509272],[-14.121406,11.677117],[-13.9008,11.678719],[-13.743161,11.811269],[-13.828272,12.142644],[-13.718744,12.247186],[-13.700476,12.586183],[-13.217818,12.575874],[-12.499051,12.33209],[-12.278599,12.35444],[-12.203565,12.465648],[-11.658301,12.386583],[-11.513943,12.442988],[-11.456169,12.076834],[-11.297574,12.077971],[-11.036556,12.211245],[-10.87083,12.177887],[-10.593224,11.923975],[-10.165214,11.844084],[-9.890993,12.060479],[-9.567912,12.194243],[-9.327616,12.334286],[-9.127474,12.30806],[-8.905265,12.088358],[-8.786099,11.812561],[-8.376305,11.393646],[-8.581305,11.136246],[-8.620321,10.810891],[-8.407311,10.909257],[-8.282357,10.792597],[-8.335377,10.494812],[-8.029944,10.206535],[-8.229337,10.12902],[-8.309616,9.789532],[-8.079114,9.376224],[-7.8321,8.575704],[-8.203499,8.455453],[-8.299049,8.316444],[-8.221792,8.123329],[-8.280703,7.68718],[-8.439298,7.686043]]]},"id":"GIN"},
{"type":"Feature","properties":{"name":"Gambia"},"geometry":{"type":"Polygon","coordinates":[[[-16.841525,13.151394],[-16.713729,13.594959],[-15.624596,13.623587],[-15.39877,13.860369],[-15.081735,13.876492],[-14.687031,13.630357],[-14.376714,13.62568],[-14.046992,13.794068],[-13.844963,13.505042],[-14.277702,13.280585],[-14.712197,13.298207],[-15.141163,13.509512],[-15.511813,13.27857],[-15.691001,13.270353],[-15.931296,13.130284],[-16.841525,13.151394]]]},"id":"GMB"},
{"type":"Feature","properties":{"name":"Guinea Bissau"},"geometry":{"type":"Polygon","coordinates":[[[-15.130311,11.040412],[-15.66418,11.458474],[-16.085214,11.524594],[-16.314787,11.806515],[-16.308947,11.958702],[-16.613838,12.170911],[-16.677452,12.384852],[-16.147717,12.547762],[-15.816574,12.515567],[-15.548477,12.62817],[-13.700476,12.586183],[-13.718744,12.247186],[-13.828272,12.142644],[-13.743161,11.811269],[-13.9008,11.678719],[-14.121406,11.677117],[-14.382192,11.509272],[-14.685687,11.527824],[-15.130311,11.040412]]]},"id":"GNB"},
{"type":"Feature","properties":{"name":"Equatorial Guinea"},"geometry":{"type":"Polygon","coordinates":[[[9.492889,1.01012],[9.305613,1.160911],[9.649158,2.283866],[11.276449,2.261051],[11.285079,1.057662],[9.830284,1.067894],[9.492889,1.01012]]]},"id":"GNQ"},
{"type":"Feature","properties":{"name":"Greece"},"geometry":{"type":"MultiPolygon","coordinates":[[[[23.69998,35.705004],[24.246665,35.368022],[25.025015,35.424996],[25.769208,35.354018],[25.745023,35.179998],[26.290003,35.29999],[26.164998,35.004995],[24.724982,34.919988],[24.735007,35.084991],[23.514978,35.279992],[23.69998,35.705004]]],[[[26.604196,41.562115],[26.294602,40.936261],[26.056942,40.824123],[25.447677,40.852545],[24.925848,40.947062],[23.714811,40.687129],[24.407999,40.124993],[23.899968,39.962006],[23.342999,39.960998],[22.813988,40.476005],[22.626299,40.256561],[22.849748,39.659311],[23.350027,39.190011],[22.973099,38.970903],[23.530016,38.510001],[24.025025,38.219993],[24.040011,37.655015],[23.115003,37.920011],[23.409972,37.409991],[22.774972,37.30501],[23.154225,36.422506],[22.490028,36.41],[21.670026,36.844986],[21.295011,37.644989],[21.120034,38.310323],[20.730032,38.769985],[20.217712,39.340235],[20.150016,39.624998],[20.615,40.110007],[20.674997,40.435],[20.99999,40.580004],[21.02004,40.842727],[21.674161,40.931275],[22.055378,41.149866],[22.597308,41.130487],[22.76177,41.3048],[22.952377,41.337994],[23.692074,41.309081],[24.492645,41.583896],[25.197201,41.234486],[26.106138,41.328899],[26.117042,41.826905],[26.604196,41.562115]]]]},"id":"GRC"},
{"type":"Feature","properties":{"name":"Greenland"},"geometry":{"type":"Polygon","coordinates":[[[-46.76379,82.62796],[-43.40644,83.22516],[-39.89753,83.18018],[-38.62214,83.54905],[-35.08787,83.64513],[-27.10046,83.51966],[-20.84539,82.72669],[-22.69182,82.34165],[-26.51753,82.29765],[-31.9,82.2],[-31.39646,82.02154],[-27.85666,82.13178],[-24.84448,81.78697],[-22.90328,82.09317],[-22.07175,81.73449],[-23.16961,81.15271],[-20.62363,81.52462],[-15.76818,81.91245],[-12.77018,81.71885],[-12.20855,81.29154],[-16.28533,80.58004],[-16.85,80.35],[-20.04624,80.17708],[-17.73035,80.12912],[-18.9,79.4],[-19.70499,78.75128],[-19.67353,77.63859],[-18.47285,76.98565],[-20.03503,76.94434],[-21.67944,76.62795],[-19.83407,76.09808],[-19.59896,75.24838],[-20.66818,75.15585],[-19.37281,74.29561],[-21.59422,74.22382],[-20.43454,73.81713],[-20.76234,73.46436],[-22.17221,73.30955],[-23.56593,73.30663],[-22.31311,72.62928],[-22.29954,72.18409],[-24.27834,72.59788],[-24.79296,72.3302],[-23.44296,72.08016],[-22.13281,71.46898],[-21.75356,70.66369],[-23.53603,70.471],[-24.30702,70.85649],[-25.54341,71.43094],[-25.20135,70.75226],[-26.36276,70.22646],[-23.72742,70.18401],[-22.34902,70.12946],[-25.02927,69.2588],[-27.74737,68.47046],[-30.67371,68.12503],[-31.77665,68.12078],[-32.81105,67.73547],[-34.20196,66.67974],[-36.35284,65.9789],[-37.04378,65.93768],[-38.37505,65.69213],[-39.81222,65.45848],[-40.66899,64.83997],[-40.68281,64.13902],[-41.1887,63.48246],[-42.81938,62.68233],[-42.41666,61.90093],[-42.86619,61.07404],[-43.3784,60.09772],[-44.7875,60.03676],[-46.26364,60.85328],[-48.26294,60.85843],[-49.23308,61.40681],[-49.90039,62.38336],[-51.63325,63.62691],[-52.14014,64.27842],[-52.27659,65.1767],[-53.66166,66.09957],[-53.30161,66.8365],[-53.96911,67.18899],[-52.9804,68.35759],[-51.47536,68.72958],[-51.08041,69.14781],[-50.87122,69.9291],[-52.013585,69.574925],[-52.55792,69.42616],[-53.45629,69.283625],[-54.68336,69.61003],[-54.75001,70.28932],[-54.35884,70.821315],[-53.431315,70.835755],[-51.39014,70.56978],[-53.10937,71.20485],[-54.00422,71.54719],[-55,71.406537],[-55.83468,71.65444],[-54.71819,72.58625],[-55.32634,72.95861],[-56.12003,73.64977],[-57.32363,74.71026],[-58.59679,75.09861],[-58.58516,75.51727],[-61.26861,76.10238],[-63.39165,76.1752],[-66.06427,76.13486],[-68.50438,76.06141],[-69.66485,76.37975],[-71.40257,77.00857],[-68.77671,77.32312],[-66.76397,77.37595],[-71.04293,77.63595],[-73.297,78.04419],[-73.15938,78.43271],[-69.37345,78.91388],[-65.7107,79.39436],[-65.3239,79.75814],[-68.02298,80.11721],[-67.15129,80.51582],[-63.68925,81.21396],[-62.23444,81.3211],[-62.65116,81.77042],[-60.28249,82.03363],[-57.20744,82.19074],[-54.13442,82.19962],[-53.04328,81.88833],[-50.39061,82.43883],[-48.00386,82.06481],[-46.59984,81.985945],[-44.523,81.6607],[-46.9007,82.19979],[-46.76379,82.62796]]]},"id":"GRL"},
{"type":"Feature","properties":{"name":"Guatemala"},"geometry":{"type":"Polygon","coordinates":[[[-90.095555,13.735338],[-90.608624,13.909771],[-91.23241,13.927832],[-91.689747,14.126218],[-92.22775,14.538829],[-92.20323,14.830103],[-92.087216,15.064585],[-92.229249,15.251447],[-91.74796,16.066565],[-90.464473,16.069562],[-90.438867,16.41011],[-90.600847,16.470778],[-90.711822,16.687483],[-91.08167,16.918477],[-91.453921,17.252177],[-91.002269,17.254658],[-91.00152,17.817595],[-90.067934,17.819326],[-89.14308,17.808319],[-89.150806,17.015577],[-89.229122,15.886938],[-88.930613,15.887273],[-88.604586,15.70638],[-88.518364,15.855389],[-88.225023,15.727722],[-88.68068,15.346247],[-89.154811,15.066419],[-89.22522,14.874286],[-89.145535,14.678019],[-89.353326,14.424133],[-89.587343,14.362586],[-89.534219,14.244816],[-89.721934,14.134228],[-90.064678,13.88197],[-90.095555,13.735338]]]},"id":"GTM"},
{"type":"Feature","properties":{"name":"Guyana"},"geometry":{"type":"Polygon","coordinates":[[[-59.758285,8.367035],[-59.101684,7.999202],[-58.482962,7.347691],[-58.454876,6.832787],[-58.078103,6.809094],[-57.542219,6.321268],[-57.147436,5.97315],[-57.307246,5.073567],[-57.914289,4.812626],[-57.86021,4.576801],[-58.044694,4.060864],[-57.601569,3.334655],[-57.281433,3.333492],[-57.150098,2.768927],[-56.539386,1.899523],[-56.782704,1.863711],[-57.335823,1.948538],[-57.660971,1.682585],[-58.11345,1.507195],[-58.429477,1.463942],[-58.540013,1.268088],[-59.030862,1.317698],[-59.646044,1.786894],[-59.718546,2.24963],[-59.974525,2.755233],[-59.815413,3.606499],[-59.53804,3.958803],[-59.767406,4.423503],[-60.111002,4.574967],[-59.980959,5.014061],[-60.213683,5.244486],[-60.733574,5.200277],[-61.410303,5.959068],[-61.139415,6.234297],[-61.159336,6.696077],[-60.543999,6.856584],[-60.295668,7.043911],[-60.637973,7.415],[-60.550588,7.779603],[-59.758285,8.367035]]]},"id":"GUY"},
{"type":"Feature","properties":{"name":"Honduras"},"geometry":{"type":"Polygon","coordinates":[[[-87.316654,12.984686],[-87.489409,13.297535],[-87.793111,13.38448],[-87.723503,13.78505],[-87.859515,13.893312],[-88.065343,13.964626],[-88.503998,13.845486],[-88.541231,13.980155],[-88.843073,14.140507],[-89.058512,14.340029],[-89.353326,14.424133],[-89.145535,14.678019],[-89.22522,14.874286],[-89.154811,15.066419],[-88.68068,15.346247],[-88.225023,15.727722],[-88.121153,15.688655],[-87.901813,15.864458],[-87.61568,15.878799],[-87.522921,15.797279],[-87.367762,15.84694],[-86.903191,15.756713],[-86.440946,15.782835],[-86.119234,15.893449],[-86.001954,16.005406],[-85.683317,15.953652],[-85.444004,15.885749],[-85.182444,15.909158],[-84.983722,15.995923],[-84.52698,15.857224],[-84.368256,15.835158],[-84.063055,15.648244],[-83.773977,15.424072],[-83.410381,15.270903],[-83.147219,14.995829],[-83.489989,15.016267],[-83.628585,14.880074],[-83.975721,14.749436],[-84.228342,14.748764],[-84.449336,14.621614],[-84.649582,14.666805],[-84.820037,14.819587],[-84.924501,14.790493],[-85.052787,14.551541],[-85.148751,14.560197],[-85.165365,14.35437],[-85.514413,14.079012],[-85.698665,13.960078],[-85.801295,13.836055],[-86.096264,14.038187],[-86.312142,13.771356],[-86.520708,13.778487],[-86.755087,13.754845],[-86.733822,13.263093],[-86.880557,13.254204],[-87.005769,13.025794],[-87.316654,12.984686]]]},"id":"HND"},
{"type":"Feature","properties":{"name":"Croatia"},"geometry":{"type":"Polygon","coordinates":[[[18.829838,45.908878],[19.072769,45.521511],[19.390476,45.236516],[19.005486,44.860234],[18.553214,45.08159],[17.861783,45.06774],[17.002146,45.233777],[16.534939,45.211608],[16.318157,45.004127],[15.959367,45.233777],[15.750026,44.818712],[16.23966,44.351143],[16.456443,44.04124],[16.916156,43.667722],[17.297373,43.446341],[17.674922,43.028563],[18.56,42.65],[18.450016,42.479991],[17.50997,42.849995],[16.930006,43.209998],[16.015385,43.507215],[15.174454,44.243191],[15.37625,44.317915],[14.920309,44.738484],[14.901602,45.07606],[14.258748,45.233777],[13.952255,44.802124],[13.656976,45.136935],[13.679403,45.484149],[13.71506,45.500324],[14.411968,45.466166],[14.595109,45.634941],[14.935244,45.471695],[15.327675,45.452316],[15.323954,45.731783],[15.67153,45.834154],[15.768733,46.238108],[16.564808,46.503751],[16.882515,46.380632],[17.630066,45.951769],[18.456062,45.759481],[18.829838,45.908878]]]},"id":"HRV"},
{"type":"Feature","properties":{"name":"Haiti"},"geometry":{"type":"Polygon","coordinates":[[[-73.189791,19.915684],[-72.579673,19.871501],[-71.712361,19.714456],[-71.624873,19.169838],[-71.701303,18.785417],[-71.945112,18.6169],[-71.687738,18.31666],[-71.708305,18.044997],[-72.372476,18.214961],[-72.844411,18.145611],[-73.454555,18.217906],[-73.922433,18.030993],[-74.458034,18.34255],[-74.369925,18.664908],[-73.449542,18.526053],[-72.694937,18.445799],[-72.334882,18.668422],[-72.79165,19.101625],[-72.784105,19.483591],[-73.415022,19.639551],[-73.189791,19.915684]]]},"id":"HTI"},
{"type":"Feature","properties":{"name":"Hungary"},"geometry":{"type":"Polygon","coordinates":[[[16.202298,46.852386],[16.534268,47.496171],[16.340584,47.712902],[16.903754,47.714866],[16.979667,48.123497],[17.488473,47.867466],[17.857133,47.758429],[18.696513,47.880954],[18.777025,48.081768],[19.174365,48.111379],[19.661364,48.266615],[19.769471,48.202691],[20.239054,48.327567],[20.473562,48.56285],[20.801294,48.623854],[21.872236,48.319971],[22.085608,48.422264],[22.64082,48.15024],[22.710531,47.882194],[22.099768,47.672439],[21.626515,46.994238],[21.021952,46.316088],[20.220192,46.127469],[19.596045,46.17173],[18.829838,45.908878],[18.456062,45.759481],[17.630066,45.951769],[16.882515,46.380632],[16.564808,46.503751],[16.370505,46.841327],[16.202298,46.852386]]]},"id":"HUN"},
{"type":"Feature","properties":{"name":"Indonesia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[120.715609,-10.239581],[120.295014,-10.25865],[118.967808,-9.557969],[119.90031,-9.36134],[120.425756,-9.665921],[120.775502,-9.969675],[120.715609,-10.239581]]],[[[124.43595,-10.140001],[123.579982,-10.359987],[123.459989,-10.239995],[123.550009,-9.900016],[123.980009,-9.290027],[124.968682,-8.89279],[125.07002,-9.089987],[125.08852,-9.393173],[124.43595,-10.140001]]],[[[117.900018,-8.095681],[118.260616,-8.362383],[118.87846,-8.280683],[119.126507,-8.705825],[117.970402,-8.906639],[117.277731,-9.040895],[116.740141,-9.032937],[117.083737,-8.457158],[117.632024,-8.449303],[117.900018,-8.095681]]],[[[122.903537,-8.094234],[122.756983,-8.649808],[121.254491,-8.933666],[119.924391,-8.810418],[119.920929,-8.444859],[120.715092,-8.236965],[121.341669,-8.53674],[122.007365,-8.46062],[122.903537,-8.094234]]],[[[108.623479,-6.777674],[110.539227,-6.877358],[110.759576,-6.465186],[112.614811,-6.946036],[112.978768,-7.594213],[114.478935,-7.776528],[115.705527,-8.370807],[114.564511,-8.751817],[113.464734,-8.348947],[112.559672,-8.376181],[111.522061,-8.302129],[110.58615,-8.122605],[109.427667,-7.740664],[108.693655,-7.6416],[108.277763,-7.766657],[106.454102,-7.3549],[106.280624,-6.9249],[105.365486,-6.851416],[106.051646,-5.895919],[107.265009,-5.954985],[108.072091,-6.345762],[108.486846,-6.421985],[108.623479,-6.777674]]],[[[134.724624,-6.214401],[134.210134,-6.895238],[134.112776,-6.142467],[134.290336,-5.783058],[134.499625,-5.445042],[134.727002,-5.737582],[134.724624,-6.214401]]],[[[127.249215,-3.459065],[126.874923,-3.790983],[126.183802,-3.607376],[125.989034,-3.177273],[127.000651,-3.129318],[127.249215,-3.459065]]],[[[130.471344,-3.093764],[130.834836,-3.858472],[129.990547,-3.446301],[129.155249,-3.362637],[128.590684,-3.428679],[127.898891,-3.393436],[128.135879,-2.84365],[129.370998,-2.802154],[130.471344,-3.093764]]],[[[134.143368,-1.151867],[134.422627,-2.769185],[135.457603,-3.367753],[136.293314,-2.307042],[137.440738,-1.703513],[138.329727,-1.702686],[139.184921,-2.051296],[139.926684,-2.409052],[141.00021,-2.600151],[141.017057,-5.859022],[141.033852,-9.117893],[140.143415,-8.297168],[139.127767,-8.096043],[138.881477,-8.380935],[137.614474,-8.411683],[138.039099,-7.597882],[138.668621,-7.320225],[138.407914,-6.232849],[137.92784,-5.393366],[135.98925,-4.546544],[135.164598,-4.462931],[133.66288,-3.538853],[133.367705,-4.024819],[132.983956,-4.112979],[132.756941,-3.746283],[132.753789,-3.311787],[131.989804,-2.820551],[133.066845,-2.460418],[133.780031,-2.479848],[133.696212,-2.214542],[132.232373,-2.212526],[131.836222,-1.617162],[130.94284,-1.432522],[130.519558,-0.93772],[131.867538,-0.695461],[132.380116,-0.369538],[133.985548,-0.78021],[134.143368,-1.151867]]],[[[125.240501,1.419836],[124.437035,0.427881],[123.685505,0.235593],[122.723083,0.431137],[121.056725,0.381217],[120.183083,0.237247],[120.04087,-0.519658],[120.935905,-1.408906],[121.475821,-0.955962],[123.340565,-0.615673],[123.258399,-1.076213],[122.822715,-0.930951],[122.38853,-1.516858],[121.508274,-1.904483],[122.454572,-3.186058],[122.271896,-3.5295],[123.170963,-4.683693],[123.162333,-5.340604],[122.628515,-5.634591],[122.236394,-5.282933],[122.719569,-4.464172],[121.738234,-4.851331],[121.489463,-4.574553],[121.619171,-4.188478],[120.898182,-3.602105],[120.972389,-2.627643],[120.305453,-2.931604],[120.390047,-4.097579],[120.430717,-5.528241],[119.796543,-5.6734],[119.366906,-5.379878],[119.653606,-4.459417],[119.498835,-3.494412],[119.078344,-3.487022],[118.767769,-2.801999],[119.180974,-2.147104],[119.323394,-1.353147],[119.825999,0.154254],[120.035702,0.566477],[120.885779,1.309223],[121.666817,1.013944],[122.927567,0.875192],[124.077522,0.917102],[125.065989,1.643259],[125.240501,1.419836]]],[[[128.688249,1.132386],[128.635952,0.258486],[128.12017,0.356413],[127.968034,-0.252077],[128.379999,-0.780004],[128.100016,-0.899996],[127.696475,-0.266598],[127.39949,1.011722],[127.600512,1.810691],[127.932378,2.174596],[128.004156,1.628531],[128.594559,1.540811],[128.688249,1.132386]]],[[[117.875627,1.827641],[118.996747,0.902219],[117.811858,0.784242],[117.478339,0.102475],[117.521644,-0.803723],[116.560048,-1.487661],[116.533797,-2.483517],[116.148084,-4.012726],[116.000858,-3.657037],[114.864803,-4.106984],[114.468652,-3.495704],[113.755672,-3.43917],[113.256994,-3.118776],[112.068126,-3.478392],[111.703291,-2.994442],[111.04824,-3.049426],[110.223846,-2.934032],[110.070936,-1.592874],[109.571948,-1.314907],[109.091874,-0.459507],[108.952658,0.415375],[109.069136,1.341934],[109.66326,2.006467],[109.830227,1.338136],[110.514061,0.773131],[111.159138,0.976478],[111.797548,0.904441],[112.380252,1.410121],[112.859809,1.49779],[113.80585,1.217549],[114.621355,1.430688],[115.134037,2.821482],[115.519078,3.169238],[115.865517,4.306559],[117.015214,4.306094],[117.882035,4.137551],[117.313232,3.234428],[118.04833,2.28769],[117.8756Line truncated
{"type":"Feature","properties":{"name":"India"},"geometry":{"type":"Polygon","coordinates":[[[77.837451,35.49401],[78.912269,34.321936],[78.811086,33.506198],[79.208892,32.994395],[79.176129,32.48378],[78.458446,32.618164],[78.738894,31.515906],[79.721367,30.882715],[81.111256,30.183481],[80.476721,29.729865],[80.088425,28.79447],[81.057203,28.416095],[81.999987,27.925479],[83.304249,27.364506],[84.675018,27.234901],[85.251779,26.726198],[86.024393,26.630985],[87.227472,26.397898],[88.060238,26.414615],[88.174804,26.810405],[88.043133,27.445819],[88.120441,27.876542],[88.730326,28.086865],[88.814248,27.299316],[88.835643,27.098966],[89.744528,26.719403],[90.373275,26.875724],[91.217513,26.808648],[92.033484,26.83831],[92.103712,27.452614],[91.696657,27.771742],[92.503119,27.896876],[93.413348,28.640629],[94.56599,29.277438],[95.404802,29.031717],[96.117679,29.452802],[96.586591,28.83098],[96.248833,28.411031],[97.327114,28.261583],[97.402561,27.882536],[97.051989,27.699059],[97.133999,27.083774],[96.419366,27.264589],[95.124768,26.573572],[95.155153,26.001307],[94.603249,25.162495],[94.552658,24.675238],[94.106742,23.850741],[93.325188,24.078556],[93.286327,23.043658],[93.060294,22.703111],[93.166128,22.27846],[92.672721,22.041239],[92.146035,23.627499],[91.869928,23.624346],[91.706475,22.985264],[91.158963,23.503527],[91.46773,24.072639],[91.915093,24.130414],[92.376202,24.976693],[91.799596,25.147432],[90.872211,25.132601],[89.920693,25.26975],[89.832481,25.965082],[89.355094,26.014407],[88.563049,26.446526],[88.209789,25.768066],[88.931554,25.238692],[88.306373,24.866079],[88.084422,24.501657],[88.69994,24.233715],[88.52977,23.631142],[88.876312,22.879146],[89.031961,22.055708],[88.888766,21.690588],[88.208497,21.703172],[86.975704,21.495562],[87.033169,20.743308],[86.499351,20.151638],[85.060266,19.478579],[83.941006,18.30201],[83.189217,17.671221],[82.192792,17.016636],[82.191242,16.556664],[81.692719,16.310219],[80.791999,15.951972],[80.324896,15.899185],[80.025069,15.136415],[80.233274,13.835771],[80.286294,13.006261],[79.862547,12.056215],[79.857999,10.357275],[79.340512,10.308854],[78.885345,9.546136],[79.18972,9.216544],[78.277941,8.933047],[77.941165,8.252959],[77.539898,7.965535],[76.592979,8.899276],[76.130061,10.29963],[75.746467,11.308251],[75.396101,11.781245],[74.864816,12.741936],[74.616717,13.992583],[74.443859,14.617222],[73.534199,15.990652],[73.119909,17.92857],[72.820909,19.208234],[72.824475,20.419503],[72.630533,21.356009],[71.175273,20.757441],[70.470459,20.877331],[69.16413,22.089298],[69.644928,22.450775],[69.349597,22.84318],[68.176645,23.691965],[68.842599,24.359134],[71.04324,24.356524],[70.844699,25.215102],[70.282873,25.722229],[70.168927,26.491872],[69.514393,26.940966],[70.616496,27.989196],[71.777666,27.91318],[72.823752,28.961592],[73.450638,29.976413],[74.42138,30.979815],[74.405929,31.692639],[75.258642,32.271105],[74.451559,32.7649],[74.104294,33.441473],[73.749948,34.317699],[74.240203,34.748887],[75.757061,34.504923],[76.871722,34.653544],[77.837451,35.49401]]]},"id":"IND"},
{"type":"Feature","properties":{"name":"Ireland"},"geometry":{"type":"Polygon","coordinates":[[[-6.197885,53.867565],[-6.032985,53.153164],[-6.788857,52.260118],[-8.561617,51.669301],[-9.977086,51.820455],[-9.166283,52.864629],[-9.688525,53.881363],[-8.327987,54.664519],[-7.572168,55.131622],[-7.366031,54.595841],[-7.572168,54.059956],[-6.95373,54.073702],[-6.197885,53.867565]]]},"id":"IRL"},
{"type":"Feature","properties":{"name":"Iran"},"geometry":{"type":"Polygon","coordinates":[[[53.921598,37.198918],[54.800304,37.392421],[55.511578,37.964117],[56.180375,37.935127],[56.619366,38.121394],[57.330434,38.029229],[58.436154,37.522309],[59.234762,37.412988],[60.377638,36.527383],[61.123071,36.491597],[61.210817,35.650072],[60.803193,34.404102],[60.52843,33.676446],[60.9637,33.528832],[60.536078,32.981269],[60.863655,32.18292],[60.941945,31.548075],[61.699314,31.379506],[61.781222,30.73585],[60.874248,29.829239],[61.369309,29.303276],[61.771868,28.699334],[62.72783,28.259645],[62.755426,27.378923],[63.233898,27.217047],[63.316632,26.756532],[61.874187,26.239975],[61.497363,25.078237],[59.616134,25.380157],[58.525761,25.609962],[57.397251,25.739902],[56.970766,26.966106],[56.492139,27.143305],[55.72371,26.964633],[54.71509,26.480658],[53.493097,26.812369],[52.483598,27.580849],[51.520763,27.86569],[50.852948,28.814521],[50.115009,30.147773],[49.57685,29.985715],[48.941333,30.31709],[48.567971,29.926778],[48.014568,30.452457],[48.004698,30.985137],[47.685286,30.984853],[47.849204,31.709176],[47.334661,32.469155],[46.109362,33.017287],[45.416691,33.967798],[45.64846,34.748138],[46.151788,35.093259],[46.07634,35.677383],[45.420618,35.977546],[44.77267,37.17045],[44.225756,37.971584],[44.421403,38.281281],[44.109225,39.428136],[44.79399,39.713003],[44.952688,39.335765],[45.457722,38.874139],[46.143623,38.741201],[46.50572,38.770605],[47.685079,39.508364],[48.060095,39.582235],[48.355529,39.288765],[48.010744,38.794015],[48.634375,38.270378],[48.883249,38.320245],[49.199612,37.582874],[50.147771,37.374567],[50.842354,36.872814],[52.264025,36.700422],[53.82579,36.965031],[53.921598,37.198918]]]},"id":"IRN"},
{"type":"Feature","properties":{"name":"Iraq"},"geometry":{"type":"Polygon","coordinates":[[[45.420618,35.977546],[46.07634,35.677383],[46.151788,35.093259],[45.64846,34.748138],[45.416691,33.967798],[46.109362,33.017287],[47.334661,32.469155],[47.849204,31.709176],[47.685286,30.984853],[48.004698,30.985137],[48.014568,30.452457],[48.567971,29.926778],[47.974519,29.975819],[47.302622,30.05907],[46.568713,29.099025],[44.709499,29.178891],[41.889981,31.190009],[40.399994,31.889992],[39.195468,32.161009],[38.792341,33.378686],[41.006159,34.419372],[41.383965,35.628317],[41.289707,36.358815],[41.837064,36.605854],[42.349591,37.229873],[42.779126,37.385264],[43.942259,37.256228],[44.293452,37.001514],[44.772699,37.170445],[45.420618,35.977546]]]},"id":"IRQ"},
{"type":"Feature","properties":{"name":"Iceland"},"geometry":{"type":"Polygon","coordinates":[[[-14.508695,66.455892],[-14.739637,65.808748],[-13.609732,65.126671],[-14.909834,64.364082],[-17.794438,63.678749],[-18.656246,63.496383],[-19.972755,63.643635],[-22.762972,63.960179],[-21.778484,64.402116],[-23.955044,64.89113],[-22.184403,65.084968],[-22.227423,65.378594],[-24.326184,65.611189],[-23.650515,66.262519],[-22.134922,66.410469],[-20.576284,65.732112],[-19.056842,66.276601],[-17.798624,65.993853],[-16.167819,66.526792],[-14.508695,66.455892]]]},"id":"ISL"},
{"type":"Feature","properties":{"name":"Israel"},"geometry":{"type":"Polygon","coordinates":[[[35.719918,32.709192],[35.545665,32.393992],[35.18393,32.532511],[34.974641,31.866582],[35.225892,31.754341],[34.970507,31.616778],[34.927408,31.353435],[35.397561,31.489086],[35.420918,31.100066],[34.922603,29.501326],[34.265433,31.219361],[34.556372,31.548824],[34.488107,31.605539],[34.752587,32.072926],[34.955417,32.827376],[35.098457,33.080539],[35.126053,33.0909],[35.460709,33.08904],[35.552797,33.264275],[35.821101,33.277426],[35.836397,32.868123],[35.700798,32.716014],[35.719918,32.709192]]]},"id":"ISR"},
{"type":"Feature","properties":{"name":"Italy"},"geometry":{"type":"MultiPolygon","coordinates":[[[[15.520376,38.231155],[15.160243,37.444046],[15.309898,37.134219],[15.099988,36.619987],[14.335229,36.996631],[13.826733,37.104531],[12.431004,37.61295],[12.570944,38.126381],[13.741156,38.034966],[14.761249,38.143874],[15.520376,38.231155]]],[[[9.210012,41.209991],[9.809975,40.500009],[9.669519,39.177376],[9.214818,39.240473],[8.806936,38.906618],[8.428302,39.171847],[8.388253,40.378311],[8.159998,40.950007],[8.709991,40.899984],[9.210012,41.209991]]],[[[12.376485,46.767559],[13.806475,46.509306],[13.69811,46.016778],[13.93763,45.591016],[13.141606,45.736692],[12.328581,45.381778],[12.383875,44.885374],[12.261453,44.600482],[12.589237,44.091366],[13.526906,43.587727],[14.029821,42.761008],[15.14257,41.95514],[15.926191,41.961315],[16.169897,41.740295],[15.889346,41.541082],[16.785002,41.179606],[17.519169,40.877143],[18.376687,40.355625],[18.480247,40.168866],[18.293385,39.810774],[17.73838,40.277671],[16.869596,40.442235],[16.448743,39.795401],[17.17149,39.4247],[17.052841,38.902871],[16.635088,38.843572],[16.100961,37.985899],[15.684087,37.908849],[15.687963,38.214593],[15.891981,38.750942],[16.109332,38.964547],[15.718814,39.544072],[15.413613,40.048357],[14.998496,40.172949],[14.703268,40.60455],[14.060672,40.786348],[13.627985,41.188287],[12.888082,41.25309],[12.106683,41.704535],[11.191906,42.355425],[10.511948,42.931463],[10.200029,43.920007],[9.702488,44.036279],[8.888946,44.366336],[8.428561,44.231228],[7.850767,43.767148],[7.435185,43.693845],[7.549596,44.127901],[7.007562,44.254767],[6.749955,45.028518],[7.096652,45.333099],[6.802355,45.70858],[6.843593,45.991147],[7.273851,45.776948],[7.755992,45.82449],[8.31663,46.163642],[8.489952,46.005151],[8.966306,46.036932],[9.182882,46.440215],[9.922837,46.314899],[10.363378,46.483571],[10.442701,46.893546],[11.048556,46.751359],[11.164828,46.941579],[12.153088,47.115393],[12.376485,46.767559]]]]},"id":"ITA"},
{"type":"Feature","properties":{"name":"Jamaica"},"geometry":{"type":"Polygon","coordinates":[[[-77.569601,18.490525],[-76.896619,18.400867],[-76.365359,18.160701],[-76.199659,17.886867],[-76.902561,17.868238],[-77.206341,17.701116],[-77.766023,17.861597],[-78.337719,18.225968],[-78.217727,18.454533],[-77.797365,18.524218],[-77.569601,18.490525]]]},"id":"JAM"},
{"type":"Feature","properties":{"name":"Jordan"},"geometry":{"type":"Polygon","coordinates":[[[35.545665,32.393992],[35.719918,32.709192],[36.834062,32.312938],[38.792341,33.378686],[39.195468,32.161009],[39.004886,32.010217],[37.002166,31.508413],[37.998849,30.5085],[37.66812,30.338665],[37.503582,30.003776],[36.740528,29.865283],[36.501214,29.505254],[36.068941,29.197495],[34.956037,29.356555],[34.922603,29.501326],[35.420918,31.100066],[35.397561,31.489086],[35.545252,31.782505],[35.545665,32.393992]]]},"id":"JOR"},
{"type":"Feature","properties":{"name":"Japan"},"geometry":{"type":"MultiPolygon","coordinates":[[[[134.638428,34.149234],[134.766379,33.806335],[134.203416,33.201178],[133.79295,33.521985],[133.280268,33.28957],[133.014858,32.704567],[132.363115,32.989382],[132.371176,33.463642],[132.924373,34.060299],[133.492968,33.944621],[133.904106,34.364931],[134.638428,34.149234]]],[[[140.976388,37.142074],[140.59977,36.343983],[140.774074,35.842877],[140.253279,35.138114],[138.975528,34.6676],[137.217599,34.606286],[135.792983,33.464805],[135.120983,33.849071],[135.079435,34.596545],[133.340316,34.375938],[132.156771,33.904933],[130.986145,33.885761],[132.000036,33.149992],[131.33279,31.450355],[130.686318,31.029579],[130.20242,31.418238],[130.447676,32.319475],[129.814692,32.61031],[129.408463,33.296056],[130.353935,33.604151],[130.878451,34.232743],[131.884229,34.749714],[132.617673,35.433393],[134.608301,35.731618],[135.677538,35.527134],[136.723831,37.304984],[137.390612,36.827391],[138.857602,37.827485],[139.426405,38.215962],[140.05479,39.438807],[139.883379,40.563312],[140.305783,41.195005],[141.368973,41.37856],[141.914263,39.991616],[141.884601,39.180865],[140.959489,38.174001],[140.976388,37.142074]]],[[[143.910162,44.1741],[144.613427,43.960883],[145.320825,44.384733],[145.543137,43.262088],[144.059662,42.988358],[143.18385,41.995215],[141.611491,42.678791],[141.067286,41.584594],[139.955106,41.569556],[139.817544,42.563759],[140.312087,43.333273],[141.380549,43.388825],[141.671952,44.772125],[141.967645,45.551483],[143.14287,44.510358],[143.910162,44.1741]]]]},"id":"JPN"},
{"type":"Feature","properties":{"name":"Kazakhstan"},"geometry":{"type":"Polygon","coordinates":[[[70.962315,42.266154],[70.388965,42.081308],[69.070027,41.384244],[68.632483,40.668681],[68.259896,40.662325],[67.985856,41.135991],[66.714047,41.168444],[66.510649,41.987644],[66.023392,41.994646],[66.098012,42.99766],[64.900824,43.728081],[63.185787,43.650075],[62.0133,43.504477],[61.05832,44.405817],[60.239972,44.784037],[58.689989,45.500014],[58.503127,45.586804],[55.928917,44.995858],[55.968191,41.308642],[55.455251,41.259859],[54.755345,42.043971],[54.079418,42.324109],[52.944293,42.116034],[52.50246,41.783316],[52.446339,42.027151],[52.692112,42.443895],[52.501426,42.792298],[51.342427,43.132975],[50.891292,44.031034],[50.339129,44.284016],[50.305643,44.609836],[51.278503,44.514854],[51.316899,45.245998],[52.16739,45.408391],[53.040876,45.259047],[53.220866,46.234646],[53.042737,46.853006],[52.042023,46.804637],[51.191945,47.048705],[50.034083,46.60899],[49.10116,46.39933],[48.593241,46.561034],[48.694734,47.075628],[48.057253,47.743753],[47.315231,47.715847],[46.466446,48.394152],[47.043672,49.152039],[46.751596,49.356006],[47.54948,50.454698],[48.577841,49.87476],[48.702382,50.605128],[50.766648,51.692762],[52.328724,51.718652],[54.532878,51.02624],[55.716941,50.621717],[56.777961,51.043551],[58.363291,51.063653],[59.642282,50.545442],[59.932807,50.842194],[61.337424,50.79907],[61.588003,51.272659],[59.967534,51.96042],[60.927269,52.447548],[60.739993,52.719986],[61.699986,52.979996],[60.978066,53.664993],[61.436591,54.006265],[65.178534,54.354228],[65.666876,54.601267],[68.1691,54.970392],[69.068167,55.38525],[70.865267,55.169734],[71.180131,54.133285],[72.22415,54.376655],[73.508516,54.035617],[73.425679,53.48981],[74.384845,53.546861],[76.8911,54.490524],[76.525179,54.177003],[77.800916,53.404415],[80.03556,50.864751],[80.568447,51.388336],[81.945986,50.812196],[83.383004,51.069183],[83.935115,50.889246],[84.416377,50.3114],[85.11556,50.117303],[85.54127,49.692859],[86.829357,49.826675],[87.35997,49.214981],[86.598776,48.549182],[85.768233,48.455751],[85.720484,47.452969],[85.16429,47.000956],[83.180484,47.330031],[82.458926,45.53965],[81.947071,45.317027],[79.966106,44.917517],[80.866206,43.180362],[80.18015,42.920068],[80.25999,42.349999],[79.643645,42.496683],[79.142177,42.856092],[77.658392,42.960686],[76.000354,42.988022],[75.636965,42.8779],[74.212866,43.298339],[73.645304,43.091272],[73.489758,42.500894],[71.844638,42.845395],[71.186281,42.704293],[70.962315,42.266154]]]},"id":"KAZ"},
{"type":"Feature","properties":{"name":"Kenya"},"geometry":{"type":"Polygon","coordinates":[[[40.993,-0.85829],[41.58513,-1.68325],[40.88477,-2.08255],[40.63785,-2.49979],[40.26304,-2.57309],[40.12119,-3.27768],[39.80006,-3.68116],[39.60489,-4.34653],[39.20222,-4.67677],[37.7669,-3.67712],[37.69869,-3.09699],[34.07262,-1.05982],[33.903711,-0.95],[33.893569,0.109814],[34.18,0.515],[34.6721,1.17694],[35.03599,1.90584],[34.59607,3.05374],[34.47913,3.5556],[34.005,4.249885],[34.620196,4.847123],[35.298007,5.506],[35.817448,5.338232],[35.817448,4.776966],[36.159079,4.447864],[36.855093,4.447864],[38.120915,3.598605],[38.43697,3.58851],[38.67114,3.61607],[38.89251,3.50074],[39.559384,3.42206],[39.85494,3.83879],[40.76848,4.25702],[41.1718,3.91909],[41.855083,3.918912],[40.98105,2.78452],[40.993,-0.85829]]]},"id":"KEN"},
{"type":"Feature","properties":{"name":"Kyrgyzstan"},"geometry":{"type":"Polygon","coordinates":[[[70.962315,42.266154],[71.186281,42.704293],[71.844638,42.845395],[73.489758,42.500894],[73.645304,43.091272],[74.212866,43.298339],[75.636965,42.8779],[76.000354,42.988022],[77.658392,42.960686],[79.142177,42.856092],[79.643645,42.496683],[80.25999,42.349999],[80.11943,42.123941],[78.543661,41.582243],[78.187197,41.185316],[76.904484,41.066486],[76.526368,40.427946],[75.467828,40.562072],[74.776862,40.366425],[73.822244,39.893973],[73.960013,39.660008],[73.675379,39.431237],[71.784694,39.279463],[70.549162,39.604198],[69.464887,39.526683],[69.55961,40.103211],[70.648019,39.935754],[71.014198,40.244366],[71.774875,40.145844],[73.055417,40.866033],[71.870115,41.3929],[71.157859,41.143587],[70.420022,41.519998],[71.259248,42.167711],[70.962315,42.266154]]]},"id":"KGZ"},
{"type":"Feature","properties":{"name":"Cambodia"},"geometry":{"type":"Polygon","coordinates":[[[103.49728,10.632555],[103.09069,11.153661],[102.584932,12.186595],[102.348099,13.394247],[102.988422,14.225721],[104.281418,14.416743],[105.218777,14.273212],[106.043946,13.881091],[106.496373,14.570584],[107.382727,14.202441],[107.614548,13.535531],[107.491403,12.337206],[105.810524,11.567615],[106.24967,10.961812],[105.199915,10.88931],[104.334335,10.486544],[103.49728,10.632555]]]},"id":"KHM"},
{"type":"Feature","properties":{"name":"South Korea"},"geometry":{"type":"Polygon","coordinates":[[[128.349716,38.612243],[129.21292,37.432392],[129.46045,36.784189],[129.468304,35.632141],[129.091377,35.082484],[128.18585,34.890377],[127.386519,34.475674],[126.485748,34.390046],[126.37392,34.93456],[126.559231,35.684541],[126.117398,36.725485],[126.860143,36.893924],[126.174759,37.749686],[126.237339,37.840378],[126.68372,37.804773],[127.073309,38.256115],[127.780035,38.304536],[128.205746,38.370397],[128.349716,38.612243]]]},"id":"KOR"},
{"type":"Feature","properties":{"name":"Kosovo"},"geometry":{"type":"Polygon","coordinates":[[[20.76216,42.05186],[20.71731,41.84711],[20.59023,41.85541],[20.52295,42.21787],[20.28374,42.32025],[20.0707,42.58863],[20.25758,42.81275],[20.49679,42.88469],[20.63508,43.21671],[20.81448,43.27205],[20.95651,43.13094],[21.143395,43.068685],[21.27421,42.90959],[21.43866,42.86255],[21.63302,42.67717],[21.77505,42.6827],[21.66292,42.43922],[21.54332,42.32025],[21.576636,42.245224],[21.3527,42.2068],[20.76216,42.05186]]]},"id":"OSA"},
{"type":"Feature","properties":{"name":"Kuwait"},"geometry":{"type":"Polygon","coordinates":[[[47.974519,29.975819],[48.183189,29.534477],[48.093943,29.306299],[48.416094,28.552004],[47.708851,28.526063],[47.459822,29.002519],[46.568713,29.099025],[47.302622,30.05907],[47.974519,29.975819]]]},"id":"KWT"},
{"type":"Feature","properties":{"name":"Laos"},"geometry":{"type":"Polygon","coordinates":[[[105.218777,14.273212],[105.544338,14.723934],[105.589039,15.570316],[104.779321,16.441865],[104.716947,17.428859],[103.956477,18.240954],[103.200192,18.309632],[102.998706,17.961695],[102.413005,17.932782],[102.113592,18.109102],[101.059548,17.512497],[101.035931,18.408928],[101.282015,19.462585],[100.606294,19.508344],[100.548881,20.109238],[100.115988,20.41785],[100.329101,20.786122],[101.180005,21.436573],[101.270026,21.201652],[101.80312,21.174367],[101.652018,22.318199],[102.170436,22.464753],[102.754896,21.675137],[103.203861,20.766562],[104.435,20.758733],[104.822574,19.886642],[104.183388,19.624668],[103.896532,19.265181],[105.094598,18.666975],[105.925762,17.485315],[106.556008,16.604284],[107.312706,15.908538],[107.564525,15.202173],[107.382727,14.202441],[106.496373,14.570584],[106.043946,13.881091],[105.218777,14.273212]]]},"id":"LAO"},
{"type":"Feature","properties":{"name":"Lebanon"},"geometry":{"type":"Polygon","coordinates":[[[35.821101,33.277426],[35.552797,33.264275],[35.460709,33.08904],[35.126053,33.0909],[35.482207,33.90545],[35.979592,34.610058],[35.998403,34.644914],[36.448194,34.593935],[36.61175,34.201789],[36.06646,33.824912],[35.821101,33.277426]]]},"id":"LBN"},
{"type":"Feature","properties":{"name":"Liberia"},"geometry":{"type":"Polygon","coordinates":[[[-7.712159,4.364566],[-7.974107,4.355755],[-9.004794,4.832419],[-9.91342,5.593561],[-10.765384,6.140711],[-11.438779,6.785917],[-11.199802,7.105846],[-11.146704,7.396706],[-10.695595,7.939464],[-10.230094,8.406206],[-10.016567,8.428504],[-9.755342,8.541055],[-9.33728,7.928534],[-9.403348,7.526905],[-9.208786,7.313921],[-8.926065,7.309037],[-8.722124,7.711674],[-8.439298,7.686043],[-8.485446,7.395208],[-8.385452,6.911801],[-8.60288,6.467564],[-8.311348,6.193033],[-7.993693,6.12619],[-7.570153,5.707352],[-7.539715,5.313345],[-7.635368,5.188159],[-7.712159,4.364566]]]},"id":"LBR"},
{"type":"Feature","properties":{"name":"Libya"},"geometry":{"type":"Polygon","coordinates":[[[14.8513,22.86295],[14.143871,22.491289],[13.581425,23.040506],[11.999506,23.471668],[11.560669,24.097909],[10.771364,24.562532],[10.303847,24.379313],[9.948261,24.936954],[9.910693,25.365455],[9.319411,26.094325],[9.716286,26.512206],[9.629056,27.140953],[9.756128,27.688259],[9.683885,28.144174],[9.859998,28.95999],[9.805634,29.424638],[9.48214,30.307556],[9.970017,30.539325],[10.056575,30.961831],[9.950225,31.37607],[10.636901,31.761421],[10.94479,32.081815],[11.432253,32.368903],[11.488787,33.136996],[12.66331,32.79278],[13.08326,32.87882],[13.91868,32.71196],[15.24563,32.26508],[15.71394,31.37626],[16.61162,31.18218],[18.02109,30.76357],[19.08641,30.26639],[19.57404,30.52582],[20.05335,30.98576],[19.82033,31.75179],[20.13397,32.2382],[20.85452,32.7068],[21.54298,32.8432],[22.89576,32.63858],[23.2368,32.19149],[23.60913,32.18726],[23.9275,32.01667],[24.92114,31.89936],[25.16482,31.56915],[24.80287,31.08929],[24.95762,30.6616],[24.70007,30.04419],[25,29.238655],[25,25.6825],[25,22],[25,20.00304],[23.85,20],[23.83766,19.58047],[19.84926,21.49509],[15.86085,23.40972],[14.8513,22.86295]]]},"id":"LBY"},
{"type":"Feature","properties":{"name":"Sri Lanka"},"geometry":{"type":"Polygon","coordinates":[[[81.787959,7.523055],[81.637322,6.481775],[81.21802,6.197141],[80.348357,5.96837],[79.872469,6.763463],[79.695167,8.200843],[80.147801,9.824078],[80.838818,9.268427],[81.304319,8.564206],[81.787959,7.523055]]]},"id":"LKA"},
{"type":"Feature","properties":{"name":"Lesotho"},"geometry":{"type":"Polygon","coordinates":[[[28.978263,-28.955597],[29.325166,-29.257387],[29.018415,-29.743766],[28.8484,-30.070051],[28.291069,-30.226217],[28.107205,-30.545732],[27.749397,-30.645106],[26.999262,-29.875954],[27.532511,-29.242711],[28.074338,-28.851469],[28.5417,-28.647502],[28.978263,-28.955597]]]},"id":"LSO"},
{"type":"Feature","properties":{"name":"Lithuania"},"geometry":{"type":"Polygon","coordinates":[[[22.731099,54.327537],[22.651052,54.582741],[22.757764,54.856574],[22.315724,55.015299],[21.268449,55.190482],[21.0558,56.031076],[22.201157,56.337802],[23.878264,56.273671],[24.860684,56.372528],[25.000934,56.164531],[25.533047,56.100297],[26.494331,55.615107],[26.588279,55.167176],[25.768433,54.846963],[25.536354,54.282423],[24.450684,53.905702],[23.484128,53.912498],[23.243987,54.220567],[22.731099,54.327537]]]},"id":"LTU"},
{"type":"Feature","properties":{"name":"Luxembourg"},"geometry":{"type":"Polygon","coordinates":[[[6.043073,50.128052],[6.242751,49.902226],[6.18632,49.463803],[5.897759,49.442667],[5.674052,49.529484],[5.782417,50.090328],[6.043073,50.128052]]]},"id":"LUX"},
{"type":"Feature","properties":{"name":"Latvia"},"geometry":{"type":"Polygon","coordinates":[[[21.0558,56.031076],[21.090424,56.783873],[21.581866,57.411871],[22.524341,57.753374],[23.318453,57.006236],[24.12073,57.025693],[24.312863,57.793424],[25.164594,57.970157],[25.60281,57.847529],[26.463532,57.476389],[27.288185,57.474528],[27.770016,57.244258],[27.855282,56.759326],[28.176709,56.16913],[27.10246,55.783314],[26.494331,55.615107],[25.533047,56.100297],[25.000934,56.164531],[24.860684,56.372528],[23.878264,56.273671],[22.201157,56.337802],[21.0558,56.031076]]]},"id":"LVA"},
{"type":"Feature","properties":{"name":"Morocco"},"geometry":{"type":"Polygon","coordinates":[[[-5.193863,35.755182],[-4.591006,35.330712],[-3.640057,35.399855],[-2.604306,35.179093],[-2.169914,35.168396],[-1.792986,34.527919],[-1.733455,33.919713],[-1.388049,32.864015],[-1.124551,32.651522],[-1.307899,32.262889],[-2.616605,32.094346],[-3.06898,31.724498],[-3.647498,31.637294],[-3.690441,30.896952],[-4.859646,30.501188],[-5.242129,30.000443],[-6.060632,29.7317],[-7.059228,29.579228],[-8.674116,28.841289],[-8.66559,27.656426],[-8.817809,27.656426],[-8.817828,27.656426],[-8.794884,27.120696],[-9.413037,27.088476],[-9.735343,26.860945],[-10.189424,26.860945],[-10.551263,26.990808],[-11.392555,26.883424],[-11.71822,26.104092],[-12.030759,26.030866],[-12.500963,24.770116],[-13.89111,23.691009],[-14.221168,22.310163],[-14.630833,21.86094],[-14.750955,21.5006],[-17.002962,21.420734],[-17.020428,21.42231],[-16.973248,21.885745],[-16.589137,22.158234],[-16.261922,22.67934],[-16.326414,23.017768],[-15.982611,23.723358],[-15.426004,24.359134],[-15.089332,24.520261],[-14.824645,25.103533],[-14.800926,25.636265],[-14.43994,26.254418],[-13.773805,26.618892],[-13.139942,27.640148],[-13.121613,27.654148],[-12.618837,28.038186],[-11.688919,28.148644],[-10.900957,28.832142],[-10.399592,29.098586],[-9.564811,29.933574],[-9.814718,31.177736],[-9.434793,32.038096],[-9.300693,32.564679],[-8.657476,33.240245],[-7.654178,33.697065],[-6.912544,34.110476],[-6.244342,35.145865],[-5.929994,35.759988],[-5.193863,35.755182]]]},"id":"MAR"},
{"type":"Feature","properties":{"name":"Moldova"},"geometry":{"type":"Polygon","coordinates":[[[26.619337,48.220726],[26.857824,48.368211],[27.522537,48.467119],[28.259547,48.155562],[28.670891,48.118149],[29.122698,47.849095],[29.050868,47.510227],[29.415135,47.346645],[29.559674,46.928583],[29.908852,46.674361],[29.83821,46.525326],[30.024659,46.423937],[29.759972,46.349988],[29.170654,46.379262],[29.072107,46.517678],[28.862972,46.437889],[28.933717,46.25883],[28.659987,45.939987],[28.485269,45.596907],[28.233554,45.488283],[28.054443,45.944586],[28.160018,46.371563],[28.12803,46.810476],[27.551166,47.405117],[27.233873,47.826771],[26.924176,48.123264],[26.619337,48.220726]]]},"id":"MDA"},
{"type":"Feature","properties":{"name":"Madagascar"},"geometry":{"type":"Polygon","coordinates":[[[49.543519,-12.469833],[49.808981,-12.895285],[50.056511,-13.555761],[50.217431,-14.758789],[50.476537,-15.226512],[50.377111,-15.706069],[50.200275,-16.000263],[49.860606,-15.414253],[49.672607,-15.710204],[49.863344,-16.451037],[49.774564,-16.875042],[49.498612,-17.106036],[49.435619,-17.953064],[49.041792,-19.118781],[48.548541,-20.496888],[47.930749,-22.391501],[47.547723,-23.781959],[47.095761,-24.94163],[46.282478,-25.178463],[45.409508,-25.601434],[44.833574,-25.346101],[44.03972,-24.988345],[43.763768,-24.460677],[43.697778,-23.574116],[43.345654,-22.776904],[43.254187,-22.057413],[43.433298,-21.336475],[43.893683,-21.163307],[43.89637,-20.830459],[44.374325,-20.072366],[44.464397,-19.435454],[44.232422,-18.961995],[44.042976,-18.331387],[43.963084,-17.409945],[44.312469,-16.850496],[44.446517,-16.216219],[44.944937,-16.179374],[45.502732,-15.974373],[45.872994,-15.793454],[46.312243,-15.780018],[46.882183,-15.210182],[47.70513,-14.594303],[48.005215,-14.091233],[47.869047,-13.663869],[48.293828,-13.784068],[48.84506,-13.089175],[48.863509,-12.487868],[49.194651,-12.040557],[49.543519,-12.469833]]]},"id":"MDG"},
{"type":"Feature","properties":{"name":"Mexico"},"geometry":{"type":"Polygon","coordinates":[[[-97.140008,25.869997],[-97.528072,24.992144],[-97.702946,24.272343],[-97.776042,22.93258],[-97.872367,22.444212],[-97.699044,21.898689],[-97.38896,21.411019],[-97.189333,20.635433],[-96.525576,19.890931],[-96.292127,19.320371],[-95.900885,18.828024],[-94.839063,18.562717],[-94.42573,18.144371],[-93.548651,18.423837],[-92.786114,18.524839],[-92.037348,18.704569],[-91.407903,18.876083],[-90.77187,19.28412],[-90.53359,19.867418],[-90.451476,20.707522],[-90.278618,20.999855],[-89.601321,21.261726],[-88.543866,21.493675],[-87.658417,21.458846],[-87.05189,21.543543],[-86.811982,21.331515],[-86.845908,20.849865],[-87.383291,20.255405],[-87.621054,19.646553],[-87.43675,19.472403],[-87.58656,19.04013],[-87.837191,18.259816],[-88.090664,18.516648],[-88.300031,18.499982],[-88.490123,18.486831],[-88.848344,17.883198],[-89.029857,18.001511],[-89.150909,17.955468],[-89.14308,17.808319],[-90.067934,17.819326],[-91.00152,17.817595],[-91.002269,17.254658],[-91.453921,17.252177],[-91.08167,16.918477],[-90.711822,16.687483],[-90.600847,16.470778],[-90.438867,16.41011],[-90.464473,16.069562],[-91.74796,16.066565],[-92.229249,15.251447],[-92.087216,15.064585],[-92.20323,14.830103],[-92.22775,14.538829],[-93.359464,15.61543],[-93.875169,15.940164],[-94.691656,16.200975],[-95.250227,16.128318],[-96.053382,15.752088],[-96.557434,15.653515],[-97.263592,15.917065],[-98.01303,16.107312],[-98.947676,16.566043],[-99.697397,16.706164],[-100.829499,17.171071],[-101.666089,17.649026],[-101.918528,17.91609],[-102.478132,17.975751],[-103.50099,18.292295],[-103.917527,18.748572],[-104.99201,19.316134],[-105.493038,19.946767],[-105.731396,20.434102],[-105.397773,20.531719],[-105.500661,20.816895],[-105.270752,21.076285],[-105.265817,21.422104],[-105.603161,21.871146],[-105.693414,22.26908],[-106.028716,22.773752],[-106.90998,23.767774],[-107.915449,24.548915],[-108.401905,25.172314],[-109.260199,25.580609],[-109.444089,25.824884],[-109.291644,26.442934],[-109.801458,26.676176],[-110.391732,27.162115],[-110.641019,27.859876],[-111.178919,27.941241],[-111.759607,28.467953],[-112.228235,28.954409],[-112.271824,29.266844],[-112.809594,30.021114],[-113.163811,30.786881],[-113.148669,31.170966],[-113.871881,31.567608],[-114.205737,31.524045],[-114.776451,31.799532],[-114.9367,31.393485],[-114.771232,30.913617],[-114.673899,30.162681],[-114.330974,29.750432],[-113.588875,29.061611],[-113.424053,28.826174],[-113.271969,28.754783],[-113.140039,28.411289],[-112.962298,28.42519],[-112.761587,27.780217],[-112.457911,27.525814],[-112.244952,27.171727],[-111.616489,26.662817],[-111.284675,25.73259],[-110.987819,25.294606],[-110.710007,24.826004],[-110.655049,24.298595],[-110.172856,24.265548],[-109.771847,23.811183],[-109.409104,23.364672],[-109.433392,23.185588],[-109.854219,22.818272],[-110.031392,22.823078],[-110.295071,23.430973],[-110.949501,24.000964],[-111.670568,24.484423],[-112.182036,24.738413],[-112.148989,25.470125],[-112.300711,26.012004],[-112.777297,26.32196],[-113.464671,26.768186],[-113.59673,26.63946],[-113.848937,26.900064],[-114.465747,27.14209],[-115.055142,27.722727],[-114.982253,27.7982],[-114.570366,27.741485],[-114.199329,28.115003],[-114.162018,28.566112],[-114.931842,29.279479],[-115.518654,29.556362],[-115.887365,30.180794],[-116.25835,30.836464],[-116.721526,31.635744],[-117.12776,32.53534],[-115.99135,32.61239],[-114.72139,32.72083],[-114.815,32.52528],[-113.30498,32.03914],[-111.02361,31.33472],[-109.035,31.34194],[-108.24194,31.34222],[-108.24,31.754854],[-106.50759,31.75452],[-106.1429,31.39995],[-105.63159,31.08383],[-105.03737,30.64402],[-104.70575,30.12173],[-104.45697,29.57196],[-103.94,29.27],[-103.11,28.97],[-102.48,29.76],[-101.6624,29.7793],[-100.9576,29.38071],[-100.45584,28.69612],[-100.11,28.11],[-99.52,27.54],[-99.3,26.84],[-99.02,26.37],[-98.24,26.06],[-97.53,25.84],[-97.140008,25.869997]]]},"id":"MEX"},
{"type":"Feature","properties":{"name":"Macedonia"},"geometry":{"type":"Polygon","coordinates":[[[20.59023,41.85541],[20.71731,41.84711],[20.76216,42.05186],[21.3527,42.2068],[21.576636,42.245224],[21.91708,42.30364],[22.380526,42.32026],[22.881374,41.999297],[22.952377,41.337994],[22.76177,41.3048],[22.597308,41.130487],[22.055378,41.149866],[21.674161,40.931275],[21.02004,40.842727],[20.60518,41.08622],[20.46315,41.51509],[20.59023,41.85541]]]},"id":"MKD"},
{"type":"Feature","properties":{"name":"Mali"},"geometry":{"type":"Polygon","coordinates":[[[-12.17075,14.616834],[-11.834208,14.799097],[-11.666078,15.388208],[-11.349095,15.411256],[-10.650791,15.132746],[-10.086846,15.330486],[-9.700255,15.264107],[-9.550238,15.486497],[-5.537744,15.50169],[-5.315277,16.201854],[-5.488523,16.325102],[-5.971129,20.640833],[-6.453787,24.956591],[-4.923337,24.974574],[-1.550055,22.792666],[1.823228,20.610809],[2.060991,20.142233],[2.683588,19.85623],[3.146661,19.693579],[3.158133,19.057364],[4.267419,19.155265],[4.27021,16.852227],[3.723422,16.184284],[3.638259,15.56812],[2.749993,15.409525],[1.385528,15.323561],[1.015783,14.968182],[0.374892,14.928908],[-0.266257,14.924309],[-0.515854,15.116158],[-1.066363,14.973815],[-2.001035,14.559008],[-2.191825,14.246418],[-2.967694,13.79815],[-3.103707,13.541267],[-3.522803,13.337662],[-4.006391,13.472485],[-4.280405,13.228444],[-4.427166,12.542646],[-5.220942,11.713859],[-5.197843,11.375146],[-5.470565,10.95127],[-5.404342,10.370737],[-5.816926,10.222555],[-6.050452,10.096361],[-6.205223,10.524061],[-6.493965,10.411303],[-6.666461,10.430811],[-6.850507,10.138994],[-7.622759,10.147236],[-7.89959,10.297382],[-8.029944,10.206535],[-8.335377,10.494812],[-8.282357,10.792597],[-8.407311,10.909257],[-8.620321,10.810891],[-8.581305,11.136246],[-8.376305,11.393646],[-8.786099,11.812561],[-8.905265,12.088358],[-9.127474,12.30806],[-9.327616,12.334286],[-9.567912,12.194243],[-9.890993,12.060479],[-10.165214,11.844084],[-10.593224,11.923975],[-10.87083,12.177887],[-11.036556,12.211245],[-11.297574,12.077971],[-11.456169,12.076834],[-11.513943,12.442988],[-11.467899,12.754519],[-11.553398,13.141214],[-11.927716,13.422075],[-12.124887,13.994727],[-12.17075,14.616834]]]},"id":"MLI"},
{"type":"Feature","properties":{"name":"Myanmar"},"geometry":{"type":"Polygon","coordinates":[[[99.543309,20.186598],[98.959676,19.752981],[98.253724,19.708203],[97.797783,18.62708],[97.375896,18.445438],[97.859123,17.567946],[98.493761,16.837836],[98.903348,16.177824],[98.537376,15.308497],[98.192074,15.123703],[98.430819,14.622028],[99.097755,13.827503],[99.212012,13.269294],[99.196354,12.804748],[99.587286,11.892763],[99.038121,10.960546],[98.553551,9.93296],[98.457174,10.675266],[98.764546,11.441292],[98.428339,12.032987],[98.509574,13.122378],[98.103604,13.64046],[97.777732,14.837286],[97.597072,16.100568],[97.16454,16.928734],[96.505769,16.427241],[95.369352,15.71439],[94.808405,15.803454],[94.188804,16.037936],[94.533486,17.27724],[94.324817,18.213514],[93.540988,19.366493],[93.663255,19.726962],[93.078278,19.855145],[92.368554,20.670883],[92.303234,21.475485],[92.652257,21.324048],[92.672721,22.041239],[93.166128,22.27846],[93.060294,22.703111],[93.286327,23.043658],[93.325188,24.078556],[94.106742,23.850741],[94.552658,24.675238],[94.603249,25.162495],[95.155153,26.001307],[95.124768,26.573572],[96.419366,27.264589],[97.133999,27.083774],[97.051989,27.699059],[97.402561,27.882536],[97.327114,28.261583],[97.911988,28.335945],[98.246231,27.747221],[98.68269,27.508812],[98.712094,26.743536],[98.671838,25.918703],[97.724609,25.083637],[97.60472,23.897405],[98.660262,24.063286],[98.898749,23.142722],[99.531992,22.949039],[99.240899,22.118314],[99.983489,21.742937],[100.416538,21.558839],[101.150033,21.849984],[101.180005,21.436573],[100.329101,20.786122],[100.115988,20.41785],[99.543309,20.186598]]]},"id":"MMR"},
{"type":"Feature","properties":{"name":"Montenegro"},"geometry":{"type":"Polygon","coordinates":[[[19.801613,42.500093],[19.738051,42.688247],[19.30449,42.19574],[19.37177,41.87755],[19.16246,41.95502],[18.88214,42.28151],[18.45,42.48],[18.56,42.65],[18.70648,43.20011],[19.03165,43.43253],[19.21852,43.52384],[19.48389,43.35229],[19.63,43.21378],[19.95857,43.10604],[20.3398,42.89852],[20.25758,42.81275],[20.0707,42.58863],[19.801613,42.500093]]]},"id":"MNE"},
{"type":"Feature","properties":{"name":"Mongolia"},"geometry":{"type":"Polygon","coordinates":[[[87.751264,49.297198],[88.805567,49.470521],[90.713667,50.331812],[92.234712,50.802171],[93.104219,50.49529],[94.147566,50.480537],[94.815949,50.013433],[95.814028,49.977467],[97.259728,49.726061],[98.231762,50.422401],[97.82574,51.010995],[98.861491,52.047366],[99.981732,51.634006],[100.88948,51.516856],[102.065223,51.259921],[102.255909,50.510561],[103.676545,50.089966],[104.621552,50.275329],[105.886591,50.406019],[106.888804,50.274296],[107.868176,49.793705],[108.475167,49.282548],[109.402449,49.292961],[110.662011,49.130128],[111.581231,49.377968],[112.89774,49.543565],[114.362456,50.248303],[114.96211,50.140247],[115.485695,49.805177],[116.678801,49.888531],[116.191802,49.134598],[115.485282,48.135383],[115.742837,47.726545],[116.308953,47.85341],[117.295507,47.697709],[118.064143,48.06673],[118.866574,47.74706],[119.772824,47.048059],[119.66327,46.69268],[118.874326,46.805412],[117.421701,46.672733],[116.717868,46.388202],[115.985096,45.727235],[114.460332,45.339817],[113.463907,44.808893],[112.436062,45.011646],[111.873306,45.102079],[111.348377,44.457442],[111.667737,44.073176],[111.829588,43.743118],[111.129682,43.406834],[110.412103,42.871234],[109.243596,42.519446],[107.744773,42.481516],[106.129316,42.134328],[104.964994,41.59741],[104.522282,41.908347],[103.312278,41.907468],[101.83304,42.514873],[100.845866,42.663804],[99.515817,42.524691],[97.451757,42.74889],[96.349396,42.725635],[95.762455,43.319449],[95.306875,44.241331],[94.688929,44.352332],[93.480734,44.975472],[92.133891,45.115076],[90.94554,45.286073],[90.585768,45.719716],[90.970809,46.888146],[90.280826,47.693549],[88.854298,48.069082],[88.013832,48.599463],[87.751264,49.297198]]]},"id":"MNG"},
{"type":"Feature","properties":{"name":"Mozambique"},"geometry":{"type":"Polygon","coordinates":[[[34.559989,-11.52002],[35.312398,-11.439146],[36.514082,-11.720938],[36.775151,-11.594537],[37.471284,-11.568751],[37.827645,-11.268769],[38.427557,-11.285202],[39.52103,-10.896854],[40.316589,-10.317096],[40.478387,-10.765441],[40.437253,-11.761711],[40.560811,-12.639177],[40.59962,-14.201975],[40.775475,-14.691764],[40.477251,-15.406294],[40.089264,-16.100774],[39.452559,-16.720891],[38.538351,-17.101023],[37.411133,-17.586368],[36.281279,-18.659688],[35.896497,-18.84226],[35.1984,-19.552811],[34.786383,-19.784012],[34.701893,-20.497043],[35.176127,-21.254361],[35.373428,-21.840837],[35.385848,-22.14],[35.562546,-22.09],[35.533935,-23.070788],[35.371774,-23.535359],[35.60747,-23.706563],[35.458746,-24.12261],[35.040735,-24.478351],[34.215824,-24.816314],[33.01321,-25.357573],[32.574632,-25.727318],[32.660363,-26.148584],[32.915955,-26.215867],[32.83012,-26.742192],[32.071665,-26.73382],[31.985779,-26.29178],[31.837778,-25.843332],[31.752408,-25.484284],[31.930589,-24.369417],[31.670398,-23.658969],[31.191409,-22.25151],[32.244988,-21.116489],[32.508693,-20.395292],[32.659743,-20.30429],[32.772708,-19.715592],[32.611994,-19.419383],[32.654886,-18.67209],[32.849861,-17.979057],[32.847639,-16.713398],[32.328239,-16.392074],[31.852041,-16.319417],[31.636498,-16.07199],[31.173064,-15.860944],[30.338955,-15.880839],[30.274256,-15.507787],[30.179481,-14.796099],[33.214025,-13.97186],[33.7897,-14.451831],[34.064825,-14.35995],[34.459633,-14.61301],[34.517666,-15.013709],[34.307291,-15.478641],[34.381292,-16.18356],[35.03381,-16.8013],[35.339063,-16.10744],[35.771905,-15.896859],[35.686845,-14.611046],[35.267956,-13.887834],[34.907151,-13.565425],[34.559989,-13.579998],[34.280006,-12.280025],[34.559989,-11.52002]]]},"id":"MOZ"},
{"type":"Feature","properties":{"name":"Mauritania"},"geometry":{"type":"Polygon","coordinates":[[[-12.17075,14.616834],[-12.830658,15.303692],[-13.435738,16.039383],[-14.099521,16.304302],[-14.577348,16.598264],[-15.135737,16.587282],[-15.623666,16.369337],[-16.12069,16.455663],[-16.463098,16.135036],[-16.549708,16.673892],[-16.270552,17.166963],[-16.146347,18.108482],[-16.256883,19.096716],[-16.377651,19.593817],[-16.277838,20.092521],[-16.536324,20.567866],[-17.063423,20.999752],[-16.845194,21.333323],[-12.929102,21.327071],[-13.118754,22.77122],[-12.874222,23.284832],[-11.937224,23.374594],[-11.969419,25.933353],[-8.687294,25.881056],[-8.6844,27.395744],[-4.923337,24.974574],[-6.453787,24.956591],[-5.971129,20.640833],[-5.488523,16.325102],[-5.315277,16.201854],[-5.537744,15.50169],[-9.550238,15.486497],[-9.700255,15.264107],[-10.086846,15.330486],[-10.650791,15.132746],[-11.349095,15.411256],[-11.666078,15.388208],[-11.834208,14.799097],[-12.17075,14.616834]]]},"id":"MRT"},
{"type":"Feature","properties":{"name":"Malawi"},"geometry":{"type":"Polygon","coordinates":[[[34.559989,-11.52002],[34.280006,-12.280025],[34.559989,-13.579998],[34.907151,-13.565425],[35.267956,-13.887834],[35.686845,-14.611046],[35.771905,-15.896859],[35.339063,-16.10744],[35.03381,-16.8013],[34.381292,-16.18356],[34.307291,-15.478641],[34.517666,-15.013709],[34.459633,-14.61301],[34.064825,-14.35995],[33.7897,-14.451831],[33.214025,-13.97186],[32.688165,-13.712858],[32.991764,-12.783871],[33.306422,-12.435778],[33.114289,-11.607198],[33.31531,-10.79655],[33.485688,-10.525559],[33.231388,-9.676722],[32.759375,-9.230599],[33.739729,-9.417151],[33.940838,-9.693674],[34.280006,-10.16],[34.559989,-11.52002]]]},"id":"MWI"},
{"type":"Feature","properties":{"name":"Malaysia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[101.075516,6.204867],[101.154219,5.691384],[101.814282,5.810808],[102.141187,6.221636],[102.371147,6.128205],[102.961705,5.524495],[103.381215,4.855001],[103.438575,4.181606],[103.332122,3.726698],[103.429429,3.382869],[103.502448,2.791019],[103.854674,2.515454],[104.247932,1.631141],[104.228811,1.293048],[103.519707,1.226334],[102.573615,1.967115],[101.390638,2.760814],[101.27354,3.270292],[100.695435,3.93914],[100.557408,4.76728],[100.196706,5.312493],[100.30626,6.040562],[100.085757,6.464489],[100.259596,6.642825],[101.075516,6.204867]]],[[[118.618321,4.478202],[117.882035,4.137551],[117.015214,4.306094],[115.865517,4.306559],[115.519078,3.169238],[115.134037,2.821482],[114.621355,1.430688],[113.80585,1.217549],[112.859809,1.49779],[112.380252,1.410121],[111.797548,0.904441],[111.159138,0.976478],[110.514061,0.773131],[109.830227,1.338136],[109.66326,2.006467],[110.396135,1.663775],[111.168853,1.850637],[111.370081,2.697303],[111.796928,2.885897],[112.995615,3.102395],[113.712935,3.893509],[114.204017,4.525874],[114.659596,4.007637],[114.869557,4.348314],[115.347461,4.316636],[115.4057,4.955228],[115.45071,5.44773],[116.220741,6.143191],[116.725103,6.924771],[117.129626,6.928053],[117.643393,6.422166],[117.689075,5.98749],[118.347691,5.708696],[119.181904,5.407836],[119.110694,5.016128],[118.439727,4.966519],[118.618321,4.478202]]]]},"id":"MYS"},
{"type":"Feature","properties":{"name":"Namibia"},"geometry":{"type":"Polygon","coordinates":[[[16.344977,-28.576705],[15.601818,-27.821247],[15.210472,-27.090956],[14.989711,-26.117372],[14.743214,-25.39292],[14.408144,-23.853014],[14.385717,-22.656653],[14.257714,-22.111208],[13.868642,-21.699037],[13.352498,-20.872834],[12.826845,-19.673166],[12.608564,-19.045349],[11.794919,-18.069129],[11.734199,-17.301889],[12.215461,-17.111668],[12.814081,-16.941343],[13.462362,-16.971212],[14.058501,-17.423381],[14.209707,-17.353101],[18.263309,-17.309951],[18.956187,-17.789095],[21.377176,-17.930636],[23.215048,-17.523116],[24.033862,-17.295843],[24.682349,-17.353411],[25.07695,-17.578823],[25.084443,-17.661816],[24.520705,-17.887125],[24.217365,-17.889347],[23.579006,-18.281261],[23.196858,-17.869038],[21.65504,-18.219146],[20.910641,-18.252219],[20.881134,-21.814327],[19.895458,-21.849157],[19.895768,-24.76779],[19.894734,-28.461105],[19.002127,-28.972443],[18.464899,-29.045462],[17.836152,-28.856378],[17.387497,-28.783514],[17.218929,-28.355943],[16.824017,-28.082162],[16.344977,-28.576705]]]},"id":"NAM"},
{"type":"Feature","properties":{"name":"New Caledonia"},"geometry":{"type":"Polygon","coordinates":[[[165.77999,-21.080005],[166.599991,-21.700019],[167.120011,-22.159991],[166.740035,-22.399976],[166.189732,-22.129708],[165.474375,-21.679607],[164.829815,-21.14982],[164.167995,-20.444747],[164.029606,-20.105646],[164.459967,-20.120012],[165.020036,-20.459991],[165.460009,-20.800022],[165.77999,-21.080005]]]},"id":"NCL"},
{"type":"Feature","properties":{"name":"Niger"},"geometry":{"type":"Polygon","coordinates":[[[2.154474,11.94015],[2.177108,12.625018],[1.024103,12.851826],[0.993046,13.33575],[0.429928,13.988733],[0.295646,14.444235],[0.374892,14.928908],[1.015783,14.968182],[1.385528,15.323561],[2.749993,15.409525],[3.638259,15.56812],[3.723422,16.184284],[4.27021,16.852227],[4.267419,19.155265],[5.677566,19.601207],[8.572893,21.565661],[11.999506,23.471668],[13.581425,23.040506],[14.143871,22.491289],[14.8513,22.86295],[15.096888,21.308519],[15.471077,21.048457],[15.487148,20.730415],[15.903247,20.387619],[15.685741,19.95718],[15.300441,17.92795],[15.247731,16.627306],[13.972202,15.684366],[13.540394,14.367134],[13.956699,13.996691],[13.954477,13.353449],[14.595781,13.330427],[14.495787,12.859396],[14.213531,12.802035],[14.181336,12.483657],[13.995353,12.461565],[13.318702,13.556356],[13.083987,13.596147],[12.302071,13.037189],[11.527803,13.32898],[10.989593,13.387323],[10.701032,13.246918],[10.114814,13.277252],[9.524928,12.851102],[9.014933,12.826659],[7.804671,13.343527],[7.330747,13.098038],[6.820442,13.115091],[6.445426,13.492768],[5.443058,13.865924],[4.368344,13.747482],[4.107946,13.531216],[3.967283,12.956109],[3.680634,12.552903],[3.61118,11.660167],[2.848643,12.235636],[2.490164,12.233052],[2.154474,11.94015]]]},"id":"NER"},
{"type":"Feature","properties":{"name":"Nigeria"},"geometry":{"type":"Polygon","coordinates":[[[8.500288,4.771983],[7.462108,4.412108],[7.082596,4.464689],[6.698072,4.240594],[5.898173,4.262453],[5.362805,4.887971],[5.033574,5.611802],[4.325607,6.270651],[3.57418,6.2583],[2.691702,6.258817],[2.749063,7.870734],[2.723793,8.506845],[2.912308,9.137608],[3.220352,9.444153],[3.705438,10.06321],[3.60007,10.332186],[3.797112,10.734746],[3.572216,11.327939],[3.61118,11.660167],[3.680634,12.552903],[3.967283,12.956109],[4.107946,13.531216],[4.368344,13.747482],[5.443058,13.865924],[6.445426,13.492768],[6.820442,13.115091],[7.330747,13.098038],[7.804671,13.343527],[9.014933,12.826659],[9.524928,12.851102],[10.114814,13.277252],[10.701032,13.246918],[10.989593,13.387323],[11.527803,13.32898],[12.302071,13.037189],[13.083987,13.596147],[13.318702,13.556356],[13.995353,12.461565],[14.181336,12.483657],[14.577178,12.085361],[14.468192,11.904752],[14.415379,11.572369],[13.57295,10.798566],[13.308676,10.160362],[13.1676,9.640626],[12.955468,9.417772],[12.753672,8.717763],[12.218872,8.305824],[12.063946,7.799808],[11.839309,7.397042],[11.745774,6.981383],[11.058788,6.644427],[10.497375,7.055358],[10.118277,7.03877],[9.522706,6.453482],[9.233163,6.444491],[8.757533,5.479666],[8.500288,4.771983]]]},"id":"NGA"},
{"type":"Feature","properties":{"name":"Nicaragua"},"geometry":{"type":"Polygon","coordinates":[[[-85.71254,11.088445],[-86.058488,11.403439],[-86.52585,11.806877],[-86.745992,12.143962],[-87.167516,12.458258],[-87.668493,12.90991],[-87.557467,13.064552],[-87.392386,12.914018],[-87.316654,12.984686],[-87.005769,13.025794],[-86.880557,13.254204],[-86.733822,13.263093],[-86.755087,13.754845],[-86.520708,13.778487],[-86.312142,13.771356],[-86.096264,14.038187],[-85.801295,13.836055],[-85.698665,13.960078],[-85.514413,14.079012],[-85.165365,14.35437],[-85.148751,14.560197],[-85.052787,14.551541],[-84.924501,14.790493],[-84.820037,14.819587],[-84.649582,14.666805],[-84.449336,14.621614],[-84.228342,14.748764],[-83.975721,14.749436],[-83.628585,14.880074],[-83.489989,15.016267],[-83.147219,14.995829],[-83.233234,14.899866],[-83.284162,14.676624],[-83.182126,14.310703],[-83.4125,13.970078],[-83.519832,13.567699],[-83.552207,13.127054],[-83.498515,12.869292],[-83.473323,12.419087],[-83.626104,12.32085],[-83.719613,11.893124],[-83.650858,11.629032],[-83.85547,11.373311],[-83.808936,11.103044],[-83.655612,10.938764],[-83.895054,10.726839],[-84.190179,10.79345],[-84.355931,10.999226],[-84.673069,11.082657],[-84.903003,10.952303],[-85.561852,11.217119],[-85.71254,11.088445]]]},"id":"NIC"},
{"type":"Feature","properties":{"name":"Netherlands"},"geometry":{"type":"Polygon","coordinates":[[[6.074183,53.510403],[6.90514,53.482162],[7.092053,53.144043],[6.84287,52.22844],[6.589397,51.852029],[5.988658,51.851616],[6.156658,50.803721],[5.606976,51.037298],[4.973991,51.475024],[4.047071,51.267259],[3.314971,51.345755],[3.830289,51.620545],[4.705997,53.091798],[6.074183,53.510403]]]},"id":"NLD"},
{"type":"Feature","properties":{"name":"Norway"},"geometry":{"type":"MultiPolygon","coordinates":[[[[28.165547,71.185474],[31.293418,70.453788],[30.005435,70.186259],[31.101079,69.55808],[29.399581,69.156916],[28.59193,69.064777],[29.015573,69.766491],[27.732292,70.164193],[26.179622,69.825299],[25.689213,69.092114],[24.735679,68.649557],[23.66205,68.891247],[22.356238,68.841741],[21.244936,69.370443],[20.645593,69.106247],[20.025269,69.065139],[19.87856,68.407194],[17.993868,68.567391],[17.729182,68.010552],[16.768879,68.013937],[16.108712,67.302456],[15.108411,66.193867],[13.55569,64.787028],[13.919905,64.445421],[13.571916,64.049114],[12.579935,64.066219],[11.930569,63.128318],[11.992064,61.800362],[12.631147,61.293572],[12.300366,60.117933],[11.468272,59.432393],[11.027369,58.856149],[10.356557,59.469807],[8.382,58.313288],[7.048748,58.078884],[5.665835,58.588155],[5.308234,59.663232],[4.992078,61.970998],[5.9129,62.614473],[8.553411,63.454008],[10.527709,64.486038],[12.358347,65.879726],[14.761146,67.810642],[16.435927,68.563205],[19.184028,69.817444],[21.378416,70.255169],[23.023742,70.202072],[24.546543,71.030497],[26.37005,70.986262],[28.165547,71.185474]]],[[[24.72412,77.85385],[22.49032,77.44493],[20.72601,77.67704],[21.41611,77.93504],[20.8119,78.25463],[22.88426,78.45494],[23.28134,78.07954],[24.72412,77.85385]]],[[[18.25183,79.70175],[21.54383,78.95611],[19.02737,78.5626],[18.47172,77.82669],[17.59441,77.63796],[17.1182,76.80941],[15.91315,76.77045],[13.76259,77.38035],[14.66956,77.73565],[13.1706,78.02493],[11.22231,78.8693],[10.44453,79.65239],[13.17077,80.01046],[13.71852,79.66039],[15.14282,79.67431],[15.52255,80.01608],[16.99085,80.05086],[18.25183,79.70175]]],[[[25.447625,80.40734],[27.407506,80.056406],[25.924651,79.517834],[23.024466,79.400012],[20.075188,79.566823],[19.897266,79.842362],[18.462264,79.85988],[17.368015,80.318896],[20.455992,80.598156],[21.907945,80.357679],[22.919253,80.657144],[25.447625,80.40734]]]]},"id":"NOR"},
{"type":"Feature","properties":{"name":"Nepal"},"geometry":{"type":"Polygon","coordinates":[[[88.120441,27.876542],[88.043133,27.445819],[88.174804,26.810405],[88.060238,26.414615],[87.227472,26.397898],[86.024393,26.630985],[85.251779,26.726198],[84.675018,27.234901],[83.304249,27.364506],[81.999987,27.925479],[81.057203,28.416095],[80.088425,28.79447],[80.476721,29.729865],[81.111256,30.183481],[81.525804,30.422717],[82.327513,30.115268],[83.337115,29.463732],[83.898993,29.320226],[84.23458,28.839894],[85.011638,28.642774],[85.82332,28.203576],[86.954517,27.974262],[88.120441,27.876542]]]},"id":"NPL"},
{"type":"Feature","properties":{"name":"New Zealand"},"geometry":{"type":"MultiPolygon","coordinates":[[[[173.020375,-40.919052],[173.247234,-41.331999],[173.958405,-40.926701],[174.247587,-41.349155],[174.248517,-41.770008],[173.876447,-42.233184],[173.22274,-42.970038],[172.711246,-43.372288],[173.080113,-43.853344],[172.308584,-43.865694],[171.452925,-44.242519],[171.185138,-44.897104],[170.616697,-45.908929],[169.831422,-46.355775],[169.332331,-46.641235],[168.411354,-46.619945],[167.763745,-46.290197],[166.676886,-46.219917],[166.509144,-45.852705],[167.046424,-45.110941],[168.303763,-44.123973],[168.949409,-43.935819],[169.667815,-43.555326],[170.52492,-43.031688],[171.12509,-42.512754],[171.569714,-41.767424],[171.948709,-41.514417],[172.097227,-40.956104],[172.79858,-40.493962],[173.020375,-40.919052]]],[[[174.612009,-36.156397],[175.336616,-37.209098],[175.357596,-36.526194],[175.808887,-36.798942],[175.95849,-37.555382],[176.763195,-37.881253],[177.438813,-37.961248],[178.010354,-37.579825],[178.517094,-37.695373],[178.274731,-38.582813],[177.97046,-39.166343],[177.206993,-39.145776],[176.939981,-39.449736],[177.032946,-39.879943],[176.885824,-40.065978],[176.508017,-40.604808],[176.01244,-41.289624],[175.239567,-41.688308],[175.067898,-41.425895],[174.650973,-41.281821],[175.22763,-40.459236],[174.900157,-39.908933],[173.824047,-39.508854],[173.852262,-39.146602],[174.574802,-38.797683],[174.743474,-38.027808],[174.697017,-37.381129],[174.292028,-36.711092],[174.319004,-36.534824],[173.840997,-36.121981],[173.054171,-35.237125],[172.636005,-34.529107],[173.007042,-34.450662],[173.551298,-35.006183],[174.32939,-35.265496],[174.612009,-36.156397]]]]},"id":"NZL"},
{"type":"Feature","properties":{"name":"Oman"},"geometry":{"type":"MultiPolygon","coordinates":[[[[58.861141,21.114035],[58.487986,20.428986],[58.034318,20.481437],[57.826373,20.243002],[57.665762,19.736005],[57.7887,19.06757],[57.694391,18.94471],[57.234264,18.947991],[56.609651,18.574267],[56.512189,18.087113],[56.283521,17.876067],[55.661492,17.884128],[55.269939,17.632309],[55.2749,17.228354],[54.791002,16.950697],[54.239253,17.044981],[53.570508,16.707663],[53.108573,16.651051],[52.782184,17.349742],[52.00001,19.000003],[54.999982,19.999994],[55.666659,22.000001],[55.208341,22.70833],[55.234489,23.110993],[55.525841,23.524869],[55.528632,23.933604],[55.981214,24.130543],[55.804119,24.269604],[55.886233,24.920831],[56.396847,24.924732],[56.84514,24.241673],[57.403453,23.878594],[58.136948,23.747931],[58.729211,23.565668],[59.180502,22.992395],[59.450098,22.660271],[59.80806,22.533612],[59.806148,22.310525],[59.442191,21.714541],[59.282408,21.433886],[58.861141,21.114035]]],[[[56.391421,25.895991],[56.261042,25.714606],[56.070821,26.055464],[56.362017,26.395934],[56.485679,26.309118],[56.391421,25.895991]]]]},"id":"OMN"},
{"type":"Feature","properties":{"name":"Pakistan"},"geometry":{"type":"Polygon","coordinates":[[[75.158028,37.133031],[75.896897,36.666806],[76.192848,35.898403],[77.837451,35.49401],[76.871722,34.653544],[75.757061,34.504923],[74.240203,34.748887],[73.749948,34.317699],[74.104294,33.441473],[74.451559,32.7649],[75.258642,32.271105],[74.405929,31.692639],[74.42138,30.979815],[73.450638,29.976413],[72.823752,28.961592],[71.777666,27.91318],[70.616496,27.989196],[69.514393,26.940966],[70.168927,26.491872],[70.282873,25.722229],[70.844699,25.215102],[71.04324,24.356524],[68.842599,24.359134],[68.176645,23.691965],[67.443667,23.944844],[67.145442,24.663611],[66.372828,25.425141],[64.530408,25.237039],[62.905701,25.218409],[61.497363,25.078237],[61.874187,26.239975],[63.316632,26.756532],[63.233898,27.217047],[62.755426,27.378923],[62.72783,28.259645],[61.771868,28.699334],[61.369309,29.303276],[60.874248,29.829239],[62.549857,29.318572],[63.550261,29.468331],[64.148002,29.340819],[64.350419,29.560031],[65.046862,29.472181],[66.346473,29.887943],[66.381458,30.738899],[66.938891,31.304911],[67.683394,31.303154],[67.792689,31.58293],[68.556932,31.71331],[68.926677,31.620189],[69.317764,31.901412],[69.262522,32.501944],[69.687147,33.105499],[70.323594,33.358533],[69.930543,34.02012],[70.881803,33.988856],[71.156773,34.348911],[71.115019,34.733126],[71.613076,35.153203],[71.498768,35.650563],[71.262348,36.074388],[71.846292,36.509942],[72.920025,36.720007],[74.067552,36.836176],[74.575893,37.020841],[75.158028,37.133031]]]},"id":"PAK"},
{"type":"Feature","properties":{"name":"Panama"},"geometry":{"type":"Polygon","coordinates":[[[-77.881571,7.223771],[-78.214936,7.512255],[-78.429161,8.052041],[-78.182096,8.319182],[-78.435465,8.387705],[-78.622121,8.718124],[-79.120307,8.996092],[-79.557877,8.932375],[-79.760578,8.584515],[-80.164481,8.333316],[-80.382659,8.298409],[-80.480689,8.090308],[-80.00369,7.547524],[-80.276671,7.419754],[-80.421158,7.271572],[-80.886401,7.220541],[-81.059543,7.817921],[-81.189716,7.647906],[-81.519515,7.70661],[-81.721311,8.108963],[-82.131441,8.175393],[-82.390934,8.292362],[-82.820081,8.290864],[-82.850958,8.073823],[-82.965783,8.225028],[-82.913176,8.423517],[-82.829771,8.626295],[-82.868657,8.807266],[-82.719183,8.925709],[-82.927155,9.07433],[-82.932891,9.476812],[-82.546196,9.566135],[-82.187123,9.207449],[-82.207586,8.995575],[-81.808567,8.950617],[-81.714154,9.031955],[-81.439287,8.786234],[-80.947302,8.858504],[-80.521901,9.111072],[-79.9146,9.312765],[-79.573303,9.61161],[-79.021192,9.552931],[-79.05845,9.454565],[-78.500888,9.420459],[-78.055928,9.24773],[-77.729514,8.946844],[-77.353361,8.670505],[-77.474723,8.524286],[-77.242566,7.935278],[-77.431108,7.638061],[-77.753414,7.70984],[-77.881571,7.223771]]]},"id":"PAN"},
{"type":"Feature","properties":{"name":"Peru"},"geometry":{"type":"Polygon","coordinates":[[[-69.590424,-17.580012],[-69.858444,-18.092694],[-70.372572,-18.347975],[-71.37525,-17.773799],[-71.462041,-17.363488],[-73.44453,-16.359363],[-75.237883,-15.265683],[-76.009205,-14.649286],[-76.423469,-13.823187],[-76.259242,-13.535039],[-77.106192,-12.222716],[-78.092153,-10.377712],[-79.036953,-8.386568],[-79.44592,-7.930833],[-79.760578,-7.194341],[-80.537482,-6.541668],[-81.249996,-6.136834],[-80.926347,-5.690557],[-81.410943,-4.736765],[-81.09967,-4.036394],[-80.302561,-3.404856],[-80.184015,-3.821162],[-80.469295,-4.059287],[-80.442242,-4.425724],[-80.028908,-4.346091],[-79.624979,-4.454198],[-79.205289,-4.959129],[-78.639897,-4.547784],[-78.450684,-3.873097],[-77.837905,-3.003021],[-76.635394,-2.608678],[-75.544996,-1.56161],[-75.233723,-0.911417],[-75.373223,-0.152032],[-75.106625,-0.057205],[-74.441601,-0.53082],[-74.122395,-1.002833],[-73.659504,-1.260491],[-73.070392,-2.308954],[-72.325787,-2.434218],[-71.774761,-2.16979],[-71.413646,-2.342802],[-70.813476,-2.256865],[-70.047709,-2.725156],[-70.692682,-3.742872],[-70.394044,-3.766591],[-69.893635,-4.298187],[-70.794769,-4.251265],[-70.928843,-4.401591],[-71.748406,-4.593983],[-72.891928,-5.274561],[-72.964507,-5.741251],[-73.219711,-6.089189],[-73.120027,-6.629931],[-73.724487,-6.918595],[-73.723401,-7.340999],[-73.987235,-7.52383],[-73.571059,-8.424447],[-73.015383,-9.032833],[-73.226713,-9.462213],[-72.563033,-9.520194],[-72.184891,-10.053598],[-71.302412,-10.079436],[-70.481894,-9.490118],[-70.548686,-11.009147],[-70.093752,-11.123972],[-69.529678,-10.951734],[-68.66508,-12.5613],[-68.88008,-12.899729],[-68.929224,-13.602684],[-68.948887,-14.453639],[-69.339535,-14.953195],[-69.160347,-15.323974],[-69.389764,-15.660129],[-68.959635,-16.500698],[-69.590424,-17.580012]]]},"id":"PER"},
{"type":"Feature","properties":{"name":"Philippines"},"geometry":{"type":"MultiPolygon","coordinates":[[[[126.376814,8.414706],[126.478513,7.750354],[126.537424,7.189381],[126.196773,6.274294],[125.831421,7.293715],[125.363852,6.786485],[125.683161,6.049657],[125.396512,5.581003],[124.219788,6.161355],[123.93872,6.885136],[124.243662,7.36061],[123.610212,7.833527],[123.296071,7.418876],[122.825506,7.457375],[122.085499,6.899424],[121.919928,7.192119],[122.312359,8.034962],[122.942398,8.316237],[123.487688,8.69301],[123.841154,8.240324],[124.60147,8.514158],[124.764612,8.960409],[125.471391,8.986997],[125.412118,9.760335],[126.222714,9.286074],[126.306637,8.782487],[126.376814,8.414706]]],[[[123.982438,10.278779],[123.623183,9.950091],[123.309921,9.318269],[122.995883,9.022189],[122.380055,9.713361],[122.586089,9.981045],[122.837081,10.261157],[122.947411,10.881868],[123.49885,10.940624],[123.337774,10.267384],[124.077936,11.232726],[123.982438,10.278779]]],[[[118.504581,9.316383],[117.174275,8.3675],[117.664477,9.066889],[118.386914,9.6845],[118.987342,10.376292],[119.511496,11.369668],[119.689677,10.554291],[119.029458,10.003653],[118.504581,9.316383]]],[[[121.883548,11.891755],[122.483821,11.582187],[123.120217,11.58366],[123.100838,11.165934],[122.637714,10.741308],[122.00261,10.441017],[121.967367,10.905691],[122.03837,11.415841],[121.883548,11.891755]]],[[[125.502552,12.162695],[125.783465,11.046122],[125.011884,11.311455],[125.032761,10.975816],[125.277449,10.358722],[124.801819,10.134679],[124.760168,10.837995],[124.459101,10.88993],[124.302522,11.495371],[124.891013,11.415583],[124.87799,11.79419],[124.266762,12.557761],[125.227116,12.535721],[125.502552,12.162695]]],[[[121.527394,13.06959],[121.26219,12.20556],[120.833896,12.704496],[120.323436,13.466413],[121.180128,13.429697],[121.527394,13.06959]]],[[[121.321308,18.504065],[121.937601,18.218552],[122.246006,18.47895],[122.336957,18.224883],[122.174279,17.810283],[122.515654,17.093505],[122.252311,16.262444],[121.662786,15.931018],[121.50507,15.124814],[121.728829,14.328376],[122.258925,14.218202],[122.701276,14.336541],[123.950295,13.782131],[123.855107,13.237771],[124.181289,12.997527],[124.077419,12.536677],[123.298035,13.027526],[122.928652,13.55292],[122.671355,13.185836],[122.03465,13.784482],[121.126385,13.636687],[120.628637,13.857656],[120.679384,14.271016],[120.991819,14.525393],[120.693336,14.756671],[120.564145,14.396279],[120.070429,14.970869],[119.920929,15.406347],[119.883773,16.363704],[120.286488,16.034629],[120.390047,17.599081],[120.715867,18.505227],[121.321308,18.504065]]]]},"id":"PHL"},
{"type":"Feature","properties":{"name":"Papua New Guinea"},"geometry":{"type":"MultiPolygon","coordinates":[[[[155.880026,-6.819997],[155.599991,-6.919991],[155.166994,-6.535931],[154.729192,-5.900828],[154.514114,-5.139118],[154.652504,-5.042431],[154.759991,-5.339984],[155.062918,-5.566792],[155.547746,-6.200655],[156.019965,-6.540014],[155.880026,-6.819997]]],[[[151.982796,-5.478063],[151.459107,-5.56028],[151.30139,-5.840728],[150.754447,-6.083763],[150.241197,-6.317754],[149.709963,-6.316513],[148.890065,-6.02604],[148.318937,-5.747142],[148.401826,-5.437756],[149.298412,-5.583742],[149.845562,-5.505503],[149.99625,-5.026101],[150.139756,-5.001348],[150.236908,-5.53222],[150.807467,-5.455842],[151.089672,-5.113693],[151.647881,-4.757074],[151.537862,-4.167807],[152.136792,-4.14879],[152.338743,-4.312966],[152.318693,-4.867661],[151.982796,-5.478063]]],[[[147.191874,-7.388024],[148.084636,-8.044108],[148.734105,-9.104664],[149.306835,-9.071436],[149.266631,-9.514406],[150.038728,-9.684318],[149.738798,-9.872937],[150.801628,-10.293687],[150.690575,-10.582713],[150.028393,-10.652476],[149.78231,-10.393267],[148.923138,-10.280923],[147.913018,-10.130441],[147.135443,-9.492444],[146.567881,-8.942555],[146.048481,-8.067414],[144.744168,-7.630128],[143.897088,-7.91533],[143.286376,-8.245491],[143.413913,-8.983069],[142.628431,-9.326821],[142.068259,-9.159596],[141.033852,-9.117893],[141.017057,-5.859022],[141.00021,-2.600151],[142.735247,-3.289153],[144.583971,-3.861418],[145.27318,-4.373738],[145.829786,-4.876498],[145.981922,-5.465609],[147.648073,-6.083659],[147.891108,-6.614015],[146.970905,-6.721657],[147.191874,-7.388024]]],[[[153.140038,-4.499983],[152.827292,-4.766427],[152.638673,-4.176127],[152.406026,-3.789743],[151.953237,-3.462062],[151.384279,-3.035422],[150.66205,-2.741486],[150.939965,-2.500002],[151.479984,-2.779985],[151.820015,-2.999972],[152.239989,-3.240009],[152.640017,-3.659983],[153.019994,-3.980015],[153.140038,-4.499983]]]]},"id":"PNG"},
{"type":"Feature","properties":{"name":"Poland"},"geometry":{"type":"Polygon","coordinates":[[[15.016996,51.106674],[14.607098,51.745188],[14.685026,52.089947],[14.4376,52.62485],[14.074521,52.981263],[14.353315,53.248171],[14.119686,53.757029],[14.8029,54.050706],[16.363477,54.513159],[17.622832,54.851536],[18.620859,54.682606],[18.696255,54.438719],[19.66064,54.426084],[20.892245,54.312525],[22.731099,54.327537],[23.243987,54.220567],[23.484128,53.912498],[23.527536,53.470122],[23.804935,53.089731],[23.799199,52.691099],[23.199494,52.486977],[23.508002,52.023647],[23.527071,51.578454],[24.029986,50.705407],[23.922757,50.424881],[23.426508,50.308506],[22.51845,49.476774],[22.776419,49.027395],[22.558138,49.085738],[21.607808,49.470107],[20.887955,49.328772],[20.415839,49.431453],[19.825023,49.217125],[19.320713,49.571574],[18.909575,49.435846],[18.853144,49.49623],[18.392914,49.988629],[17.649445,50.049038],[17.554567,50.362146],[16.868769,50.473974],[16.719476,50.215747],[16.176253,50.422607],[16.238627,50.697733],[15.490972,50.78473],[15.016996,51.106674]]]},"id":"POL"},
{"type":"Feature","properties":{"name":"Puerto Rico"},"geometry":{"type":"Polygon","coordinates":[[[-66.282434,18.514762],[-65.771303,18.426679],[-65.591004,18.228035],[-65.847164,17.975906],[-66.599934,17.981823],[-67.184162,17.946553],[-67.242428,18.37446],[-67.100679,18.520601],[-66.282434,18.514762]]]},"id":"PRI"},
{"type":"Feature","properties":{"name":"North Korea"},"geometry":{"type":"Polygon","coordinates":[[[130.640016,42.395009],[130.780007,42.220007],[130.400031,42.280004],[129.965949,41.941368],[129.667362,41.601104],[129.705189,40.882828],[129.188115,40.661808],[129.0104,40.485436],[128.633368,40.189847],[127.967414,40.025413],[127.533436,39.75685],[127.50212,39.323931],[127.385434,39.213472],[127.783343,39.050898],[128.349716,38.612243],[128.205746,38.370397],[127.780035,38.304536],[127.073309,38.256115],[126.68372,37.804773],[126.237339,37.840378],[126.174759,37.749686],[125.689104,37.94001],[125.568439,37.752089],[125.27533,37.669071],[125.240087,37.857224],[124.981033,37.948821],[124.712161,38.108346],[124.985994,38.548474],[125.221949,38.665857],[125.132859,38.848559],[125.38659,39.387958],[125.321116,39.551385],[124.737482,39.660344],[124.265625,39.928493],[125.079942,40.569824],[126.182045,41.107336],[126.869083,41.816569],[127.343783,41.503152],[128.208433,41.466772],[128.052215,41.994285],[129.596669,42.424982],[129.994267,42.985387],[130.640016,42.395009]]]},"id":"PRK"},
{"type":"Feature","properties":{"name":"Portugal"},"geometry":{"type":"Polygon","coordinates":[[[-9.034818,41.880571],[-8.671946,42.134689],[-8.263857,42.280469],[-8.013175,41.790886],[-7.422513,41.792075],[-7.251309,41.918346],[-6.668606,41.883387],[-6.389088,41.381815],[-6.851127,41.111083],[-6.86402,40.330872],[-7.026413,40.184524],[-7.066592,39.711892],[-7.498632,39.629571],[-7.098037,39.030073],[-7.374092,38.373059],[-7.029281,38.075764],[-7.166508,37.803894],[-7.537105,37.428904],[-7.453726,37.097788],[-7.855613,36.838269],[-8.382816,36.97888],[-8.898857,36.868809],[-8.746101,37.651346],[-8.839998,38.266243],[-9.287464,38.358486],[-9.526571,38.737429],[-9.446989,39.392066],[-9.048305,39.755093],[-8.977353,40.159306],[-8.768684,40.760639],[-8.790853,41.184334],[-8.990789,41.543459],[-9.034818,41.880571]]]},"id":"PRT"},
{"type":"Feature","properties":{"name":"Paraguay"},"geometry":{"type":"Polygon","coordinates":[[[-62.685057,-22.249029],[-62.291179,-21.051635],[-62.265961,-20.513735],[-61.786326,-19.633737],[-60.043565,-19.342747],[-59.115042,-19.356906],[-58.183471,-19.868399],[-58.166392,-20.176701],[-57.870674,-20.732688],[-57.937156,-22.090176],[-56.88151,-22.282154],[-56.473317,-22.0863],[-55.797958,-22.35693],[-55.610683,-22.655619],[-55.517639,-23.571998],[-55.400747,-23.956935],[-55.027902,-24.001274],[-54.652834,-23.839578],[-54.29296,-24.021014],[-54.293476,-24.5708],[-54.428946,-25.162185],[-54.625291,-25.739255],[-54.788795,-26.621786],[-55.695846,-27.387837],[-56.486702,-27.548499],[-57.60976,-27.395899],[-58.618174,-27.123719],[-57.63366,-25.603657],[-57.777217,-25.16234],[-58.807128,-24.771459],[-60.028966,-24.032796],[-60.846565,-23.880713],[-62.685057,-22.249029]]]},"id":"PRY"},
{"type":"Feature","properties":{"name":"Qatar"},"geometry":{"type":"Polygon","coordinates":[[[50.810108,24.754743],[50.743911,25.482424],[51.013352,26.006992],[51.286462,26.114582],[51.589079,25.801113],[51.6067,25.21567],[51.389608,24.627386],[51.112415,24.556331],[50.810108,24.754743]]]},"id":"QAT"},
{"type":"Feature","properties":{"name":"Romania"},"geometry":{"type":"Polygon","coordinates":[[[22.710531,47.882194],[23.142236,48.096341],[23.760958,47.985598],[24.402056,47.981878],[24.866317,47.737526],[25.207743,47.891056],[25.945941,47.987149],[26.19745,48.220881],[26.619337,48.220726],[26.924176,48.123264],[27.233873,47.826771],[27.551166,47.405117],[28.12803,46.810476],[28.160018,46.371563],[28.054443,45.944586],[28.233554,45.488283],[28.679779,45.304031],[29.149725,45.464925],[29.603289,45.293308],[29.626543,45.035391],[29.141612,44.82021],[28.837858,44.913874],[28.558081,43.707462],[27.970107,43.812468],[27.2424,44.175986],[26.065159,43.943494],[25.569272,43.688445],[24.100679,43.741051],[23.332302,43.897011],[22.944832,43.823785],[22.65715,44.234923],[22.474008,44.409228],[22.705726,44.578003],[22.459022,44.702517],[22.145088,44.478422],[21.562023,44.768947],[21.483526,45.18117],[20.874313,45.416375],[20.762175,45.734573],[20.220192,46.127469],[21.021952,46.316088],[21.626515,46.994238],[22.099768,47.672439],[22.710531,47.882194]]]},"id":"ROU"},
{"type":"Feature","properties":{"name":"Russia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[143.648007,50.7476],[144.654148,48.976391],[143.173928,49.306551],[142.558668,47.861575],[143.533492,46.836728],[143.505277,46.137908],[142.747701,46.740765],[142.09203,45.966755],[141.906925,46.805929],[142.018443,47.780133],[141.904445,48.859189],[142.1358,49.615163],[142.179983,50.952342],[141.594076,51.935435],[141.682546,53.301966],[142.606934,53.762145],[142.209749,54.225476],[142.654786,54.365881],[142.914616,53.704578],[143.260848,52.74076],[143.235268,51.75666],[143.648007,50.7476]]],[[[22.731099,54.327537],[20.892245,54.312525],[19.66064,54.426084],[19.888481,54.86616],[21.268449,55.190482],[22.315724,55.015299],[22.757764,54.856574],[22.651052,54.582741],[22.731099,54.327537]]],[[[-175.01425,66.58435],[-174.33983,66.33556],[-174.57182,67.06219],[-171.85731,66.91308],[-169.89958,65.97724],[-170.89107,65.54139],[-172.53025,65.43791],[-172.555,64.46079],[-172.95533,64.25269],[-173.89184,64.2826],[-174.65392,64.63125],[-175.98353,64.92288],[-176.20716,65.35667],[-177.22266,65.52024],[-178.35993,65.39052],[-178.90332,65.74044],[-178.68611,66.11211],[-179.88377,65.87456],[-179.43268,65.40411],[-180,64.979709],[-180,68.963636],[-177.55,68.2],[-174.92825,67.20589],[-175.01425,66.58435]]],[[[180,70.832199],[178.903425,70.78114],[178.7253,71.0988],[180,71.515714],[180,70.832199]]],[[[-178.69378,70.89302],[-180,70.832199],[-180,71.515714],[-179.871875,71.55762],[-179.02433,71.55553],[-177.577945,71.26948],[-177.663575,71.13277],[-178.69378,70.89302]]],[[[143.60385,73.21244],[142.08763,73.20544],[140.038155,73.31692],[139.86312,73.36983],[140.81171,73.76506],[142.06207,73.85758],[143.48283,73.47525],[143.60385,73.21244]]],[[[150.73167,75.08406],[149.575925,74.68892],[147.977465,74.778355],[146.11919,75.17298],[146.358485,75.49682],[148.22223,75.345845],[150.73167,75.08406]]],[[[145.086285,75.562625],[144.3,74.82],[140.61381,74.84768],[138.95544,74.61148],[136.97439,75.26167],[137.51176,75.94917],[138.831075,76.13676],[141.471615,76.09289],[145.086285,75.562625]]],[[[57.535693,70.720464],[56.944979,70.632743],[53.677375,70.762658],[53.412017,71.206662],[51.601895,71.474759],[51.455754,72.014881],[52.478275,72.229442],[52.444169,72.774731],[54.427614,73.627548],[53.50829,73.749814],[55.902459,74.627486],[55.631933,75.081412],[57.868644,75.60939],[61.170044,76.251883],[64.498368,76.439055],[66.210977,76.809782],[68.15706,76.939697],[68.852211,76.544811],[68.180573,76.233642],[64.637326,75.737755],[61.583508,75.260885],[58.477082,74.309056],[56.986786,73.333044],[55.419336,72.371268],[55.622838,71.540595],[57.535693,70.720464]]],[[[106.97013,76.97419],[107.24,76.48],[108.1538,76.72335],[111.07726,76.71],[113.33151,76.22224],[114.13417,75.84764],[113.88539,75.32779],[112.77918,75.03186],[110.15125,74.47673],[109.4,74.18],[110.64,74.04],[112.11919,73.78774],[113.01954,73.97693],[113.52958,73.33505],[113.96881,73.59488],[115.56782,73.75285],[118.77633,73.58772],[119.02,73.12],[123.20066,72.97122],[123.25777,73.73503],[125.38,73.56],[126.97644,73.56549],[128.59126,73.03871],[129.05157,72.39872],[128.46,71.98],[129.71599,71.19304],[131.28858,70.78699],[132.2535,71.8363],[133.85766,71.38642],[135.56193,71.65525],[137.49755,71.34763],[138.23409,71.62803],[139.86983,71.48783],[139.14791,72.41619],[140.46817,72.84941],[149.5,72.2],[150.35118,71.60643],[152.9689,70.84222],[157.00688,71.03141],[158.99779,70.86672],[159.83031,70.45324],[159.70866,69.72198],[160.94053,69.43728],[162.27907,69.64204],[164.05248,69.66823],[165.94037,69.47199],[167.83567,69.58269],[169.57763,68.6938],[170.81688,69.01363],[170.0082,69.65276],[170.45345,70.09703],[173.64391,69.81743],[175.72403,69.87725],[178.6,69.4],[180,68.963636],[180,64.979709],[179.99281,64.97433],[178.7072,64.53493],[177.41128,64.60821],[178.313,64.07593],[178.90825,63.25197],[179.37034,62.98262],[179.48636,62.56894],[179.22825,62.3041],[177.3643,62.5219],[174.56929,61.76915],[173.68013,61.65261],[172.15,60.95],[170.6985,60.33618],[170.33085,59.88177],[168.90046,60.57355],[166.29498,59.78855],[165.84,60.16],[164.87674,59.7316],[163.53929,59.86871],[163.21711,59.21101],[162.01733,58.24328],[162.05297,57.83912],[163.19191,57.61503],[163.05794,56.15924],[162.12958,56.12219],[161.70146,55.28568],[162.11749,54.85514],[160.36877,54.34433],[160.02173,53.20257],[158.53094,52.95868],[158.23118,51.94269],[156.78979,51.01105],[156.42,51.7],[155.99182,53.15895],[155.43366,55.38103],[155.91442,56.76792],[156.75815,57.3647],[156.81035,57.83204],[158.36433,58.05575],[160.15064,59.31477],[161.87204,60.343],[163.66969,61.1409],[164.47355,62.55061],[163.25842,62.46627],[162.65791,61.6425],[160.12148,60.54423],[159.30232,61.77396],[156.72068,61.43442],[154.21806,59.75818],[155.04375,59.14495],[152.81185,58.88385],[151.26573,58.78089],[151.33815,59.50396],[149.78371,59.65573],[148.54481,59.16448],[145.48722,59.33637],[142.19782,59.03998],[138.95848,57.08805],[135.12619,54.72959],[136.70171,54.6Line truncated
{"type":"Feature","properties":{"name":"Rwanda"},"geometry":{"type":"Polygon","coordinates":[[[30.419105,-1.134659],[30.816135,-1.698914],[30.758309,-2.28725],[30.469696,-2.413858],[29.938359,-2.348487],[29.632176,-2.917858],[29.024926,-2.839258],[29.117479,-2.292211],[29.254835,-2.21511],[29.291887,-1.620056],[29.579466,-1.341313],[29.821519,-1.443322],[30.419105,-1.134659]]]},"id":"RWA"},
{"type":"Feature","properties":{"name":"Western Sahara"},"geometry":{"type":"Polygon","coordinates":[[[-8.794884,27.120696],[-8.817828,27.656426],[-8.66559,27.656426],[-8.665124,27.589479],[-8.6844,27.395744],[-8.687294,25.881056],[-11.969419,25.933353],[-11.937224,23.374594],[-12.874222,23.284832],[-13.118754,22.77122],[-12.929102,21.327071],[-16.845194,21.333323],[-17.063423,20.999752],[-17.020428,21.42231],[-17.002962,21.420734],[-14.750955,21.5006],[-14.630833,21.86094],[-14.221168,22.310163],[-13.89111,23.691009],[-12.500963,24.770116],[-12.030759,26.030866],[-11.71822,26.104092],[-11.392555,26.883424],[-10.551263,26.990808],[-10.189424,26.860945],[-9.735343,26.860945],[-9.413037,27.088476],[-8.794884,27.120696]]]},"id":"ESH"},
{"type":"Feature","properties":{"name":"Saudi Arabia"},"geometry":{"type":"Polygon","coordinates":[[[42.779332,16.347891],[42.649573,16.774635],[42.347989,17.075806],[42.270888,17.474722],[41.754382,17.833046],[41.221391,18.6716],[40.939341,19.486485],[40.247652,20.174635],[39.801685,20.338862],[39.139399,21.291905],[39.023696,21.986875],[39.066329,22.579656],[38.492772,23.688451],[38.02386,24.078686],[37.483635,24.285495],[37.154818,24.858483],[37.209491,25.084542],[36.931627,25.602959],[36.639604,25.826228],[36.249137,26.570136],[35.640182,27.37652],[35.130187,28.063352],[34.632336,28.058546],[34.787779,28.607427],[34.83222,28.957483],[34.956037,29.356555],[36.068941,29.197495],[36.501214,29.505254],[36.740528,29.865283],[37.503582,30.003776],[37.66812,30.338665],[37.998849,30.5085],[37.002166,31.508413],[39.004886,32.010217],[39.195468,32.161009],[40.399994,31.889992],[41.889981,31.190009],[44.709499,29.178891],[46.568713,29.099025],[47.459822,29.002519],[47.708851,28.526063],[48.416094,28.552004],[48.807595,27.689628],[49.299554,27.461218],[49.470914,27.109999],[50.152422,26.689663],[50.212935,26.277027],[50.113303,25.943972],[50.239859,25.60805],[50.527387,25.327808],[50.660557,24.999896],[50.810108,24.754743],[51.112415,24.556331],[51.389608,24.627386],[51.579519,24.245497],[51.617708,24.014219],[52.000733,23.001154],[55.006803,22.496948],[55.208341,22.70833],[55.666659,22.000001],[54.999982,19.999994],[52.00001,19.000003],[49.116672,18.616668],[48.183344,18.166669],[47.466695,17.116682],[47.000005,16.949999],[46.749994,17.283338],[46.366659,17.233315],[45.399999,17.333335],[45.216651,17.433329],[44.062613,17.410359],[43.791519,17.319977],[43.380794,17.579987],[43.115798,17.08844],[43.218375,16.66689],[42.779332,16.347891]]]},"id":"SAU"},
{"type":"Feature","properties":{"name":"Sudan"},"geometry":{"type":"Polygon","coordinates":[[[33.963393,9.464285],[33.824963,9.484061],[33.842131,9.981915],[33.721959,10.325262],[33.206938,10.720112],[33.086766,11.441141],[33.206938,12.179338],[32.743419,12.248008],[32.67475,12.024832],[32.073892,11.97333],[32.314235,11.681484],[32.400072,11.080626],[31.850716,10.531271],[31.352862,9.810241],[30.837841,9.707237],[29.996639,10.290927],[29.618957,10.084919],[29.515953,9.793074],[29.000932,9.604232],[28.966597,9.398224],[27.97089,9.398224],[27.833551,9.604232],[27.112521,9.638567],[26.752006,9.466893],[26.477328,9.55273],[25.962307,10.136421],[25.790633,10.411099],[25.069604,10.27376],[24.794926,9.810241],[24.537415,8.917538],[24.194068,8.728696],[23.88698,8.61973],[23.805813,8.666319],[23.459013,8.954286],[23.394779,9.265068],[23.55725,9.681218],[23.554304,10.089255],[22.977544,10.714463],[22.864165,11.142395],[22.87622,11.38461],[22.50869,11.67936],[22.49762,12.26024],[22.28801,12.64605],[21.93681,12.58818],[22.03759,12.95546],[22.29658,13.37232],[22.18329,13.78648],[22.51202,14.09318],[22.30351,14.32682],[22.56795,14.94429],[23.02459,15.68072],[23.88689,15.61084],[23.83766,19.58047],[23.85,20],[25,20.00304],[25,22],[29.02,22],[32.9,22],[36.86623,22],[37.18872,21.01885],[36.96941,20.83744],[37.1147,19.80796],[37.48179,18.61409],[37.86276,18.36786],[38.41009,17.998307],[37.904,17.42754],[37.16747,17.26314],[36.85253,16.95655],[36.75389,16.29186],[36.32322,14.82249],[36.42951,14.42211],[36.27022,13.56333],[35.86363,12.57828],[35.26049,12.08286],[34.83163,11.31896],[34.73115,10.91017],[34.25745,10.63009],[33.96162,9.58358],[33.963393,9.464285]]]},"id":"SDN"},
{"type":"Feature","properties":{"name":"South Sudan"},"geometry":{"type":"Polygon","coordinates":[[[33.963393,9.464285],[33.97498,8.68456],[33.8255,8.37916],[33.2948,8.35458],[32.95418,7.78497],[33.56829,7.71334],[34.0751,7.22595],[34.25032,6.82607],[34.70702,6.59422],[35.298007,5.506],[34.620196,4.847123],[34.005,4.249885],[33.39,3.79],[32.68642,3.79232],[31.88145,3.55827],[31.24556,3.7819],[30.83385,3.50917],[29.95349,4.1737],[29.715995,4.600805],[29.159078,4.389267],[28.696678,4.455077],[28.428994,4.287155],[27.979977,4.408413],[27.374226,5.233944],[27.213409,5.550953],[26.465909,5.946717],[26.213418,6.546603],[25.796648,6.979316],[25.124131,7.500085],[25.114932,7.825104],[24.567369,8.229188],[23.88698,8.61973],[24.194068,8.728696],[24.537415,8.917538],[24.794926,9.810241],[25.069604,10.27376],[25.790633,10.411099],[25.962307,10.136421],[26.477328,9.55273],[26.752006,9.466893],[27.112521,9.638567],[27.833551,9.604232],[27.97089,9.398224],[28.966597,9.398224],[29.000932,9.604232],[29.515953,9.793074],[29.618957,10.084919],[29.996639,10.290927],[30.837841,9.707237],[31.352862,9.810241],[31.850716,10.531271],[32.400072,11.080626],[32.314235,11.681484],[32.073892,11.97333],[32.67475,12.024832],[32.743419,12.248008],[33.206938,12.179338],[33.086766,11.441141],[33.206938,10.720112],[33.721959,10.325262],[33.842131,9.981915],[33.824963,9.484061],[33.963393,9.464285]]]},"id":"SDS"},
{"type":"Feature","properties":{"name":"Senegal"},"geometry":{"type":"Polygon","coordinates":[[[-16.713729,13.594959],[-17.126107,14.373516],[-17.625043,14.729541],[-17.185173,14.919477],[-16.700706,15.621527],[-16.463098,16.135036],[-16.12069,16.455663],[-15.623666,16.369337],[-15.135737,16.587282],[-14.577348,16.598264],[-14.099521,16.304302],[-13.435738,16.039383],[-12.830658,15.303692],[-12.17075,14.616834],[-12.124887,13.994727],[-11.927716,13.422075],[-11.553398,13.141214],[-11.467899,12.754519],[-11.513943,12.442988],[-11.658301,12.386583],[-12.203565,12.465648],[-12.278599,12.35444],[-12.499051,12.33209],[-13.217818,12.575874],[-13.700476,12.586183],[-15.548477,12.62817],[-15.816574,12.515567],[-16.147717,12.547762],[-16.677452,12.384852],[-16.841525,13.151394],[-15.931296,13.130284],[-15.691001,13.270353],[-15.511813,13.27857],[-15.141163,13.509512],[-14.712197,13.298207],[-14.277702,13.280585],[-13.844963,13.505042],[-14.046992,13.794068],[-14.376714,13.62568],[-14.687031,13.630357],[-15.081735,13.876492],[-15.39877,13.860369],[-15.624596,13.623587],[-16.713729,13.594959]]]},"id":"SEN"},
{"type":"Feature","properties":{"name":"Solomon Islands"},"geometry":{"type":"MultiPolygon","coordinates":[[[[162.119025,-10.482719],[162.398646,-10.826367],[161.700032,-10.820011],[161.319797,-10.204751],[161.917383,-10.446701],[162.119025,-10.482719]]],[[[160.852229,-9.872937],[160.462588,-9.89521],[159.849447,-9.794027],[159.640003,-9.63998],[159.702945,-9.24295],[160.362956,-9.400304],[160.688518,-9.610162],[160.852229,-9.872937]]],[[[161.679982,-9.599982],[161.529397,-9.784312],[160.788253,-8.917543],[160.579997,-8.320009],[160.920028,-8.320009],[161.280006,-9.120011],[161.679982,-9.599982]]],[[[159.875027,-8.33732],[159.917402,-8.53829],[159.133677,-8.114181],[158.586114,-7.754824],[158.21115,-7.421872],[158.359978,-7.320018],[158.820001,-7.560003],[159.640003,-8.020027],[159.875027,-8.33732]]],[[[157.538426,-7.34782],[157.33942,-7.404767],[156.90203,-7.176874],[156.491358,-6.765943],[156.542828,-6.599338],[157.14,-7.021638],[157.538426,-7.34782]]]]},"id":"SLB"},
{"type":"Feature","properties":{"name":"Sierra Leone"},"geometry":{"type":"Polygon","coordinates":[[[-11.438779,6.785917],[-11.708195,6.860098],[-12.428099,7.262942],[-12.949049,7.798646],[-13.124025,8.163946],[-13.24655,8.903049],[-12.711958,9.342712],[-12.596719,9.620188],[-12.425929,9.835834],[-12.150338,9.858572],[-11.917277,10.046984],[-11.117481,10.045873],[-10.839152,9.688246],[-10.622395,9.26791],[-10.65477,8.977178],[-10.494315,8.715541],[-10.505477,8.348896],[-10.230094,8.406206],[-10.695595,7.939464],[-11.146704,7.396706],[-11.199802,7.105846],[-11.438779,6.785917]]]},"id":"SLE"},
{"type":"Feature","properties":{"name":"El Salvador"},"geometry":{"type":"Polygon","coordinates":[[[-87.793111,13.38448],[-87.904112,13.149017],[-88.483302,13.163951],[-88.843228,13.259734],[-89.256743,13.458533],[-89.812394,13.520622],[-90.095555,13.735338],[-90.064678,13.88197],[-89.721934,14.134228],[-89.534219,14.244816],[-89.587343,14.362586],[-89.353326,14.424133],[-89.058512,14.340029],[-88.843073,14.140507],[-88.541231,13.980155],[-88.503998,13.845486],[-88.065343,13.964626],[-87.859515,13.893312],[-87.723503,13.78505],[-87.793111,13.38448]]]},"id":"SLV"},
{"type":"Feature","properties":{"name":"Somaliland"},"geometry":{"type":"Polygon","coordinates":[[[48.93813,9.451749],[48.486736,8.837626],[47.78942,8.003],[46.948328,7.996877],[43.67875,9.18358],[43.296975,9.540477],[42.92812,10.02194],[42.55876,10.57258],[42.776852,10.926879],[43.145305,11.46204],[43.47066,11.27771],[43.666668,10.864169],[44.117804,10.445538],[44.614259,10.442205],[45.556941,10.698029],[46.645401,10.816549],[47.525658,11.127228],[48.021596,11.193064],[48.378784,11.375482],[48.948206,11.410622],[48.942005,11.394266],[48.938491,10.982327],[48.938233,9.9735],[48.93813,9.451749]]]},"id":"ABV"},
{"type":"Feature","properties":{"name":"Somalia"},"geometry":{"type":"Polygon","coordinates":[[[49.72862,11.5789],[50.25878,11.67957],[50.73202,12.0219],[51.1112,12.02464],[51.13387,11.74815],[51.04153,11.16651],[51.04531,10.6409],[50.83418,10.27972],[50.55239,9.19874],[50.07092,8.08173],[49.4527,6.80466],[48.59455,5.33911],[47.74079,4.2194],[46.56476,2.85529],[45.56399,2.04576],[44.06815,1.05283],[43.13597,0.2922],[42.04157,-0.91916],[41.81095,-1.44647],[41.58513,-1.68325],[40.993,-0.85829],[40.98105,2.78452],[41.855083,3.918912],[42.12861,4.23413],[42.76967,4.25259],[43.66087,4.95755],[44.9636,5.00162],[47.78942,8.003],[48.486736,8.837626],[48.93813,9.451749],[48.938233,9.9735],[48.938491,10.982327],[48.942005,11.394266],[48.948205,11.410617],[49.26776,11.43033],[49.72862,11.5789]]]},"id":"SOM"},
{"type":"Feature","properties":{"name":"Republic of Serbia"},"geometry":{"type":"Polygon","coordinates":[[[20.874313,45.416375],[21.483526,45.18117],[21.562023,44.768947],[22.145088,44.478422],[22.459022,44.702517],[22.705726,44.578003],[22.474008,44.409228],[22.65715,44.234923],[22.410446,44.008063],[22.500157,43.642814],[22.986019,43.211161],[22.604801,42.898519],[22.436595,42.580321],[22.545012,42.461362],[22.380526,42.32026],[21.91708,42.30364],[21.576636,42.245224],[21.54332,42.32025],[21.66292,42.43922],[21.77505,42.6827],[21.63302,42.67717],[21.43866,42.86255],[21.27421,42.90959],[21.143395,43.068685],[20.95651,43.13094],[20.81448,43.27205],[20.63508,43.21671],[20.49679,42.88469],[20.25758,42.81275],[20.3398,42.89852],[19.95857,43.10604],[19.63,43.21378],[19.48389,43.35229],[19.21852,43.52384],[19.454,43.5681],[19.59976,44.03847],[19.11761,44.42307],[19.36803,44.863],[19.00548,44.86023],[19.390476,45.236516],[19.072769,45.521511],[18.82982,45.90888],[19.596045,46.17173],[20.220192,46.127469],[20.762175,45.734573],[20.874313,45.416375]]]},"id":"SRB"},
{"type":"Feature","properties":{"name":"Suriname"},"geometry":{"type":"Polygon","coordinates":[[[-57.147436,5.97315],[-55.949318,5.772878],[-55.84178,5.953125],[-55.03325,6.025291],[-53.958045,5.756548],[-54.478633,4.896756],[-54.399542,4.212611],[-54.006931,3.620038],[-54.181726,3.18978],[-54.269705,2.732392],[-54.524754,2.311849],[-55.097587,2.523748],[-55.569755,2.421506],[-55.973322,2.510364],[-56.073342,2.220795],[-55.9056,2.021996],[-55.995698,1.817667],[-56.539386,1.899523],[-57.150098,2.768927],[-57.281433,3.333492],[-57.601569,3.334655],[-58.044694,4.060864],[-57.86021,4.576801],[-57.914289,4.812626],[-57.307246,5.073567],[-57.147436,5.97315]]]},"id":"SUR"},
{"type":"Feature","properties":{"name":"Slovakia"},"geometry":{"type":"Polygon","coordinates":[[[18.853144,49.49623],[18.909575,49.435846],[19.320713,49.571574],[19.825023,49.217125],[20.415839,49.431453],[20.887955,49.328772],[21.607808,49.470107],[22.558138,49.085738],[22.280842,48.825392],[22.085608,48.422264],[21.872236,48.319971],[20.801294,48.623854],[20.473562,48.56285],[20.239054,48.327567],[19.769471,48.202691],[19.661364,48.266615],[19.174365,48.111379],[18.777025,48.081768],[18.696513,47.880954],[17.857133,47.758429],[17.488473,47.867466],[16.979667,48.123497],[16.879983,48.470013],[16.960288,48.596982],[17.101985,48.816969],[17.545007,48.800019],[17.886485,48.903475],[17.913512,48.996493],[18.104973,49.043983],[18.170498,49.271515],[18.399994,49.315001],[18.554971,49.495015],[18.853144,49.49623]]]},"id":"SVK"},
{"type":"Feature","properties":{"name":"Slovenia"},"geometry":{"type":"Polygon","coordinates":[[[13.806475,46.509306],[14.632472,46.431817],[15.137092,46.658703],[16.011664,46.683611],[16.202298,46.852386],[16.370505,46.841327],[16.564808,46.503751],[15.768733,46.238108],[15.67153,45.834154],[15.323954,45.731783],[15.327675,45.452316],[14.935244,45.471695],[14.595109,45.634941],[14.411968,45.466166],[13.71506,45.500324],[13.93763,45.591016],[13.69811,46.016778],[13.806475,46.509306]]]},"id":"SVN"},
{"type":"Feature","properties":{"name":"Sweden"},"geometry":{"type":"Polygon","coordinates":[[[22.183173,65.723741],[21.213517,65.026005],[21.369631,64.413588],[19.778876,63.609554],[17.847779,62.7494],[17.119555,61.341166],[17.831346,60.636583],[18.787722,60.081914],[17.869225,58.953766],[16.829185,58.719827],[16.44771,57.041118],[15.879786,56.104302],[14.666681,56.200885],[14.100721,55.407781],[12.942911,55.361737],[12.625101,56.30708],[11.787942,57.441817],[11.027369,58.856149],[11.468272,59.432393],[12.300366,60.117933],[12.631147,61.293572],[11.992064,61.800362],[11.930569,63.128318],[12.579935,64.066219],[13.571916,64.049114],[13.919905,64.445421],[13.55569,64.787028],[15.108411,66.193867],[16.108712,67.302456],[16.768879,68.013937],[17.729182,68.010552],[17.993868,68.567391],[19.87856,68.407194],[20.025269,69.065139],[20.645593,69.106247],[21.978535,68.616846],[23.539473,67.936009],[23.56588,66.396051],[23.903379,66.006927],[22.183173,65.723741]]]},"id":"SWE"},
{"type":"Feature","properties":{"name":"Swaziland"},"geometry":{"type":"Polygon","coordinates":[[[32.071665,-26.73382],[31.86806,-27.177927],[31.282773,-27.285879],[30.685962,-26.743845],[30.676609,-26.398078],[30.949667,-26.022649],[31.04408,-25.731452],[31.333158,-25.660191],[31.837778,-25.843332],[31.985779,-26.29178],[32.071665,-26.73382]]]},"id":"SWZ"},
{"type":"Feature","properties":{"name":"Syria"},"geometry":{"type":"Polygon","coordinates":[[[38.792341,33.378686],[36.834062,32.312938],[35.719918,32.709192],[35.700798,32.716014],[35.836397,32.868123],[35.821101,33.277426],[36.06646,33.824912],[36.61175,34.201789],[36.448194,34.593935],[35.998403,34.644914],[35.905023,35.410009],[36.149763,35.821535],[36.41755,36.040617],[36.685389,36.259699],[36.739494,36.81752],[37.066761,36.623036],[38.167727,36.90121],[38.699891,36.712927],[39.52258,36.716054],[40.673259,37.091276],[41.212089,37.074352],[42.349591,37.229873],[41.837064,36.605854],[41.289707,36.358815],[41.383965,35.628317],[41.006159,34.419372],[38.792341,33.378686]]]},"id":"SYR"},
{"type":"Feature","properties":{"name":"Chad"},"geometry":{"type":"Polygon","coordinates":[[[14.495787,12.859396],[14.595781,13.330427],[13.954477,13.353449],[13.956699,13.996691],[13.540394,14.367134],[13.97217,15.68437],[15.247731,16.627306],[15.300441,17.92795],[15.685741,19.95718],[15.903247,20.387619],[15.487148,20.730415],[15.47106,21.04845],[15.096888,21.308519],[14.8513,22.86295],[15.86085,23.40972],[19.84926,21.49509],[23.83766,19.58047],[23.88689,15.61084],[23.02459,15.68072],[22.56795,14.94429],[22.30351,14.32682],[22.51202,14.09318],[22.18329,13.78648],[22.29658,13.37232],[22.03759,12.95546],[21.93681,12.58818],[22.28801,12.64605],[22.49762,12.26024],[22.50869,11.67936],[22.87622,11.38461],[22.864165,11.142395],[22.231129,10.971889],[21.723822,10.567056],[21.000868,9.475985],[20.059685,9.012706],[19.094008,9.074847],[18.81201,8.982915],[18.911022,8.630895],[18.389555,8.281304],[17.96493,7.890914],[16.705988,7.508328],[16.456185,7.734774],[16.290562,7.754307],[16.106232,7.497088],[15.27946,7.421925],[15.436092,7.692812],[15.120866,8.38215],[14.979996,8.796104],[14.544467,8.965861],[13.954218,9.549495],[14.171466,10.021378],[14.627201,9.920919],[14.909354,9.992129],[15.467873,9.982337],[14.923565,10.891325],[14.960152,11.555574],[14.89336,12.21905],[14.495787,12.859396]]]},"id":"TCD"},
{"type":"Feature","properties":{"name":"Togo"},"geometry":{"type":"Polygon","coordinates":[[[1.865241,6.142158],[1.060122,5.928837],[0.836931,6.279979],[0.570384,6.914359],[0.490957,7.411744],[0.712029,8.312465],[0.461192,8.677223],[0.365901,9.465004],[0.36758,10.191213],[-0.049785,10.706918],[0.023803,11.018682],[0.899563,10.997339],[0.772336,10.470808],[1.077795,10.175607],[1.425061,9.825395],[1.463043,9.334624],[1.664478,9.12859],[1.618951,6.832038],[1.865241,6.142158]]]},"id":"TGO"},
{"type":"Feature","properties":{"name":"Thailand"},"geometry":{"type":"Polygon","coordinates":[[[102.584932,12.186595],[101.687158,12.64574],[100.83181,12.627085],[100.978467,13.412722],[100.097797,13.406856],[100.018733,12.307001],[99.478921,10.846367],[99.153772,9.963061],[99.222399,9.239255],[99.873832,9.207862],[100.279647,8.295153],[100.459274,7.429573],[101.017328,6.856869],[101.623079,6.740622],[102.141187,6.221636],[101.814282,5.810808],[101.154219,5.691384],[101.075516,6.204867],[100.259596,6.642825],[100.085757,6.464489],[99.690691,6.848213],[99.519642,7.343454],[98.988253,7.907993],[98.503786,8.382305],[98.339662,7.794512],[98.150009,8.350007],[98.25915,8.973923],[98.553551,9.93296],[99.038121,10.960546],[99.587286,11.892763],[99.196354,12.804748],[99.212012,13.269294],[99.097755,13.827503],[98.430819,14.622028],[98.192074,15.123703],[98.537376,15.308497],[98.903348,16.177824],[98.493761,16.837836],[97.859123,17.567946],[97.375896,18.445438],[97.797783,18.62708],[98.253724,19.708203],[98.959676,19.752981],[99.543309,20.186598],[100.115988,20.41785],[100.548881,20.109238],[100.606294,19.508344],[101.282015,19.462585],[101.035931,18.408928],[101.059548,17.512497],[102.113592,18.109102],[102.413005,17.932782],[102.998706,17.961695],[103.200192,18.309632],[103.956477,18.240954],[104.716947,17.428859],[104.779321,16.441865],[105.589039,15.570316],[105.544338,14.723934],[105.218777,14.273212],[104.281418,14.416743],[102.988422,14.225721],[102.348099,13.394247],[102.584932,12.186595]]]},"id":"THA"},
{"type":"Feature","properties":{"name":"Tajikistan"},"geometry":{"type":"Polygon","coordinates":[[[71.014198,40.244366],[70.648019,39.935754],[69.55961,40.103211],[69.464887,39.526683],[70.549162,39.604198],[71.784694,39.279463],[73.675379,39.431237],[73.928852,38.505815],[74.257514,38.606507],[74.864816,38.378846],[74.829986,37.990007],[74.980002,37.41999],[73.948696,37.421566],[73.260056,37.495257],[72.63689,37.047558],[72.193041,36.948288],[71.844638,36.738171],[71.448693,37.065645],[71.541918,37.905774],[71.239404,37.953265],[71.348131,38.258905],[70.806821,38.486282],[70.376304,38.138396],[70.270574,37.735165],[70.116578,37.588223],[69.518785,37.608997],[69.196273,37.151144],[68.859446,37.344336],[68.135562,37.023115],[67.83,37.144994],[68.392033,38.157025],[68.176025,38.901553],[67.44222,39.140144],[67.701429,39.580478],[68.536416,39.533453],[69.011633,40.086158],[69.329495,40.727824],[70.666622,40.960213],[70.45816,40.496495],[70.601407,40.218527],[71.014198,40.244366]]]},"id":"TJK"},
{"type":"Feature","properties":{"name":"Turkmenistan"},"geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[61.123071,36.491597],[60.377638,36.527383],[59.234762,37.412988],[58.436154,37.522309],[57.330434,38.029229],[56.619366,38.121394],[56.180375,37.935127],[55.511578,37.964117],[54.800304,37.392421],[53.921598,37.198918],[53.735511,37.906136],[53.880929,38.952093],[53.101028,39.290574],[53.357808,39.975286],[52.693973,40.033629],[52.915251,40.876523],[53.858139,40.631034],[54.736845,40.951015],[54.008311,41.551211],[53.721713,42.123191],[52.91675,41.868117],[52.814689,41.135371],[52.50246,41.783316],[52.944293,42.116034],[54.079418,42.324109],[54.755345,42.043971],[55.455251,41.259859],[55.968191,41.308642],[57.096391,41.32231],[56.932215,41.826026],[57.78653,42.170553],[58.629011,42.751551],[59.976422,42.223082],[60.083341,41.425146],[60.465953,41.220327],[61.547179,41.26637],[61.882714,41.084857],[62.37426,40.053886],[63.518015,39.363257],[64.170223,38.892407],[65.215999,38.402695],[66.54615,37.974685],[66.518607,37.362784],[66.217385,37.39379],[65.745631,37.661164],[65.588948,37.305217],[64.746105,37.111818],[64.546479,36.312073],[63.982896,36.007957],[63.193538,35.857166],[62.984662,35.404041],[62.230651,35.270664],[61.210817,35.650072]]]},"id":"TKM"},
{"type":"Feature","properties":{"name":"East Timor"},"geometry":{"type":"Polygon","coordinates":[[[124.968682,-8.89279],[125.086246,-8.656887],[125.947072,-8.432095],[126.644704,-8.398247],[126.957243,-8.273345],[127.335928,-8.397317],[126.967992,-8.668256],[125.925885,-9.106007],[125.08852,-9.393173],[125.07002,-9.089987],[124.968682,-8.89279]]]},"id":"TLS"},
{"type":"Feature","properties":{"name":"Trinidad and Tobago"},"geometry":{"type":"Polygon","coordinates":[[[-61.68,10.76],[-61.105,10.89],[-60.895,10.855],[-60.935,10.11],[-61.77,10],[-61.95,10.09],[-61.66,10.365],[-61.68,10.76]]]},"id":"TTO"},
{"type":"Feature","properties":{"name":"Tunisia"},"geometry":{"type":"Polygon","coordinates":[[[9.48214,30.307556],[9.055603,32.102692],[8.439103,32.506285],[8.430473,32.748337],[7.612642,33.344115],[7.524482,34.097376],[8.140981,34.655146],[8.376368,35.479876],[8.217824,36.433177],[8.420964,36.946427],[9.509994,37.349994],[10.210002,37.230002],[10.18065,36.724038],[11.028867,37.092103],[11.100026,36.899996],[10.600005,36.41],[10.593287,35.947444],[10.939519,35.698984],[10.807847,34.833507],[10.149593,34.330773],[10.339659,33.785742],[10.856836,33.76874],[11.108501,33.293343],[11.488787,33.136996],[11.432253,32.368903],[10.94479,32.081815],[10.636901,31.761421],[9.950225,31.37607],[10.056575,30.961831],[9.970017,30.539325],[9.48214,30.307556]]]},"id":"TUN"},
{"type":"Feature","properties":{"name":"Turkey"},"geometry":{"type":"MultiPolygon","coordinates":[[[[36.913127,41.335358],[38.347665,40.948586],[39.512607,41.102763],[40.373433,41.013673],[41.554084,41.535656],[42.619549,41.583173],[43.582746,41.092143],[43.752658,40.740201],[43.656436,40.253564],[44.400009,40.005],[44.79399,39.713003],[44.109225,39.428136],[44.421403,38.281281],[44.225756,37.971584],[44.772699,37.170445],[44.293452,37.001514],[43.942259,37.256228],[42.779126,37.385264],[42.349591,37.229873],[41.212089,37.074352],[40.673259,37.091276],[39.52258,36.716054],[38.699891,36.712927],[38.167727,36.90121],[37.066761,36.623036],[36.739494,36.81752],[36.685389,36.259699],[36.41755,36.040617],[36.149763,35.821535],[35.782085,36.274995],[36.160822,36.650606],[35.550936,36.565443],[34.714553,36.795532],[34.026895,36.21996],[32.509158,36.107564],[31.699595,36.644275],[30.621625,36.677865],[30.391096,36.262981],[29.699976,36.144357],[28.732903,36.676831],[27.641187,36.658822],[27.048768,37.653361],[26.318218,38.208133],[26.8047,38.98576],[26.170785,39.463612],[27.28002,40.420014],[28.819978,40.460011],[29.240004,41.219991],[31.145934,41.087622],[32.347979,41.736264],[33.513283,42.01896],[35.167704,42.040225],[36.913127,41.335358]]],[[[27.192377,40.690566],[26.358009,40.151994],[26.043351,40.617754],[26.056942,40.824123],[26.294602,40.936261],[26.604196,41.562115],[26.117042,41.826905],[27.135739,42.141485],[27.99672,42.007359],[28.115525,41.622886],[28.988443,41.299934],[28.806438,41.054962],[27.619017,40.999823],[27.192377,40.690566]]]]},"id":"TUR"},
{"type":"Feature","properties":{"name":"Taiwan"},"geometry":{"type":"Polygon","coordinates":[[[121.777818,24.394274],[121.175632,22.790857],[120.74708,21.970571],[120.220083,22.814861],[120.106189,23.556263],[120.69468,24.538451],[121.495044,25.295459],[121.951244,24.997596],[121.777818,24.394274]]]},"id":"TWN"},
{"type":"Feature","properties":{"name":"United Republic of Tanzania"},"geometry":{"type":"Polygon","coordinates":[[[33.903711,-0.95],[34.07262,-1.05982],[37.69869,-3.09699],[37.7669,-3.67712],[39.20222,-4.67677],[38.74054,-5.90895],[38.79977,-6.47566],[39.44,-6.84],[39.47,-7.1],[39.19469,-7.7039],[39.25203,-8.00781],[39.18652,-8.48551],[39.53574,-9.11237],[39.9496,-10.0984],[40.31659,-10.3171],[39.521,-10.89688],[38.427557,-11.285202],[37.82764,-11.26879],[37.47129,-11.56876],[36.775151,-11.594537],[36.514082,-11.720938],[35.312398,-11.439146],[34.559989,-11.52002],[34.28,-10.16],[33.940838,-9.693674],[33.73972,-9.41715],[32.759375,-9.230599],[32.191865,-8.930359],[31.556348,-8.762049],[31.157751,-8.594579],[30.74,-8.34],[30.2,-7.08],[29.62,-6.52],[29.419993,-5.939999],[29.519987,-5.419979],[29.339998,-4.499983],[29.753512,-4.452389],[30.11632,-4.09012],[30.50554,-3.56858],[30.75224,-3.35931],[30.74301,-3.03431],[30.52766,-2.80762],[30.46967,-2.41383],[30.758309,-2.28725],[30.816135,-1.698914],[30.419105,-1.134659],[30.76986,-1.01455],[31.86617,-1.02736],[33.903711,-0.95]]]},"id":"TZA"},
{"type":"Feature","properties":{"name":"Uganda"},"geometry":{"type":"Polygon","coordinates":[[[31.86617,-1.02736],[30.76986,-1.01455],[30.419105,-1.134659],[29.821519,-1.443322],[29.579466,-1.341313],[29.587838,-0.587406],[29.8195,-0.2053],[29.875779,0.59738],[30.086154,1.062313],[30.468508,1.583805],[30.85267,1.849396],[31.174149,2.204465],[30.77332,2.33989],[30.83385,3.50917],[31.24556,3.7819],[31.88145,3.55827],[32.68642,3.79232],[33.39,3.79],[34.005,4.249885],[34.47913,3.5556],[34.59607,3.05374],[35.03599,1.90584],[34.6721,1.17694],[34.18,0.515],[33.893569,0.109814],[33.903711,-0.95],[31.86617,-1.02736]]]},"id":"UGA"},
{"type":"Feature","properties":{"name":"Ukraine"},"geometry":{"type":"Polygon","coordinates":[[[31.785998,52.101678],[32.159412,52.061267],[32.412058,52.288695],[32.715761,52.238465],[33.7527,52.335075],[34.391731,51.768882],[34.141978,51.566413],[34.224816,51.255993],[35.022183,51.207572],[35.377924,50.773955],[35.356116,50.577197],[36.626168,50.225591],[37.39346,50.383953],[38.010631,49.915662],[38.594988,49.926462],[40.069058,49.601055],[40.080789,49.30743],[39.674664,48.783818],[39.895632,48.232405],[39.738278,47.898937],[38.770585,47.825608],[38.255112,47.5464],[38.223538,47.10219],[37.425137,47.022221],[36.759855,46.6987],[35.823685,46.645964],[34.962342,46.273197],[35.020788,45.651219],[35.510009,45.409993],[36.529998,45.46999],[36.334713,45.113216],[35.239999,44.939996],[33.882511,44.361479],[33.326421,44.564877],[33.546924,45.034771],[32.454174,45.327466],[32.630804,45.519186],[33.588162,45.851569],[33.298567,46.080598],[31.74414,46.333348],[31.675307,46.706245],[30.748749,46.5831],[30.377609,46.03241],[29.603289,45.293308],[29.149725,45.464925],[28.679779,45.304031],[28.233554,45.488283],[28.485269,45.596907],[28.659987,45.939987],[28.933717,46.25883],[28.862972,46.437889],[29.072107,46.517678],[29.170654,46.379262],[29.759972,46.349988],[30.024659,46.423937],[29.83821,46.525326],[29.908852,46.674361],[29.559674,46.928583],[29.415135,47.346645],[29.050868,47.510227],[29.122698,47.849095],[28.670891,48.118149],[28.259547,48.155562],[27.522537,48.467119],[26.857824,48.368211],[26.619337,48.220726],[26.19745,48.220881],[25.945941,47.987149],[25.207743,47.891056],[24.866317,47.737526],[24.402056,47.981878],[23.760958,47.985598],[23.142236,48.096341],[22.710531,47.882194],[22.64082,48.15024],[22.085608,48.422264],[22.280842,48.825392],[22.558138,49.085738],[22.776419,49.027395],[22.51845,49.476774],[23.426508,50.308506],[23.922757,50.424881],[24.029986,50.705407],[23.527071,51.578454],[24.005078,51.617444],[24.553106,51.888461],[25.327788,51.910656],[26.337959,51.832289],[27.454066,51.592303],[28.241615,51.572227],[28.617613,51.427714],[28.992835,51.602044],[29.254938,51.368234],[30.157364,51.416138],[30.555117,51.319503],[30.619454,51.822806],[30.927549,52.042353],[31.785998,52.101678]]]},"id":"UKR"},
{"type":"Feature","properties":{"name":"Uruguay"},"geometry":{"type":"Polygon","coordinates":[[[-57.625133,-30.216295],[-56.976026,-30.109686],[-55.973245,-30.883076],[-55.60151,-30.853879],[-54.572452,-31.494511],[-53.787952,-32.047243],[-53.209589,-32.727666],[-53.650544,-33.202004],[-53.373662,-33.768378],[-53.806426,-34.396815],[-54.935866,-34.952647],[-55.67409,-34.752659],[-56.215297,-34.859836],[-57.139685,-34.430456],[-57.817861,-34.462547],[-58.427074,-33.909454],[-58.349611,-33.263189],[-58.132648,-33.040567],[-58.14244,-32.044504],[-57.874937,-31.016556],[-57.625133,-30.216295]]]},"id":"URY"},
{"type":"Feature","properties":{"name":"USA"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-155.54211,19.08348],[-155.68817,18.91619],[-155.93665,19.05939],[-155.90806,19.33888],[-156.07347,19.70294],[-156.02368,19.81422],[-155.85008,19.97729],[-155.91907,20.17395],[-155.86108,20.26721],[-155.78505,20.2487],[-155.40214,20.07975],[-155.22452,19.99302],[-155.06226,19.8591],[-154.80741,19.50871],[-154.83147,19.45328],[-155.22217,19.23972],[-155.54211,19.08348]]],[[[-156.07926,20.64397],[-156.41445,20.57241],[-156.58673,20.783],[-156.70167,20.8643],[-156.71055,20.92676],[-156.61258,21.01249],[-156.25711,20.91745],[-155.99566,20.76404],[-156.07926,20.64397]]],[[[-156.75824,21.17684],[-156.78933,21.06873],[-157.32521,21.09777],[-157.25027,21.21958],[-156.75824,21.17684]]],[[[-157.65283,21.32217],[-157.70703,21.26442],[-157.7786,21.27729],[-158.12667,21.31244],[-158.2538,21.53919],[-158.29265,21.57912],[-158.0252,21.71696],[-157.94161,21.65272],[-157.65283,21.32217]]],[[[-159.34512,21.982],[-159.46372,21.88299],[-159.80051,22.06533],[-159.74877,22.1382],[-159.5962,22.23618],[-159.36569,22.21494],[-159.34512,21.982]]],[[[-94.81758,49.38905],[-94.64,48.84],[-94.32914,48.67074],[-93.63087,48.60926],[-92.61,48.45],[-91.64,48.14],[-90.83,48.27],[-89.6,48.01],[-89.272917,48.019808],[-88.378114,48.302918],[-87.439793,47.94],[-86.461991,47.553338],[-85.652363,47.220219],[-84.87608,46.900083],[-84.779238,46.637102],[-84.543749,46.538684],[-84.6049,46.4396],[-84.3367,46.40877],[-84.14212,46.512226],[-84.091851,46.275419],[-83.890765,46.116927],[-83.616131,46.116927],[-83.469551,45.994686],[-83.592851,45.816894],[-82.550925,45.347517],[-82.337763,44.44],[-82.137642,43.571088],[-82.43,42.98],[-82.9,42.43],[-83.12,42.08],[-83.142,41.975681],[-83.02981,41.832796],[-82.690089,41.675105],[-82.439278,41.675105],[-81.277747,42.209026],[-80.247448,42.3662],[-78.939362,42.863611],[-78.92,42.965],[-79.01,43.27],[-79.171674,43.466339],[-78.72028,43.625089],[-77.737885,43.629056],[-76.820034,43.628784],[-76.5,44.018459],[-76.375,44.09631],[-75.31821,44.81645],[-74.867,45.00048],[-73.34783,45.00738],[-71.50506,45.0082],[-71.405,45.255],[-71.08482,45.30524],[-70.66,45.46],[-70.305,45.915],[-69.99997,46.69307],[-69.237216,47.447781],[-68.905,47.185],[-68.23444,47.35486],[-67.79046,47.06636],[-67.79134,45.70281],[-67.13741,45.13753],[-66.96466,44.8097],[-68.03252,44.3252],[-69.06,43.98],[-70.11617,43.68405],[-70.645476,43.090238],[-70.81489,42.8653],[-70.825,42.335],[-70.495,41.805],[-70.08,41.78],[-70.185,42.145],[-69.88497,41.92283],[-69.96503,41.63717],[-70.64,41.475],[-71.12039,41.49445],[-71.86,41.32],[-72.295,41.27],[-72.87643,41.22065],[-73.71,40.931102],[-72.24126,41.11948],[-71.945,40.93],[-73.345,40.63],[-73.982,40.628],[-73.952325,40.75075],[-74.25671,40.47351],[-73.96244,40.42763],[-74.17838,39.70926],[-74.90604,38.93954],[-74.98041,39.1964],[-75.20002,39.24845],[-75.52805,39.4985],[-75.32,38.96],[-75.071835,38.782032],[-75.05673,38.40412],[-75.37747,38.01551],[-75.94023,37.21689],[-76.03127,37.2566],[-75.72205,37.93705],[-76.23287,38.319215],[-76.35,39.15],[-76.542725,38.717615],[-76.32933,38.08326],[-76.989998,38.239992],[-76.30162,37.917945],[-76.25874,36.9664],[-75.9718,36.89726],[-75.86804,36.55125],[-75.72749,35.55074],[-76.36318,34.80854],[-77.397635,34.51201],[-78.05496,33.92547],[-78.55435,33.86133],[-79.06067,33.49395],[-79.20357,33.15839],[-80.301325,32.509355],[-80.86498,32.0333],[-81.33629,31.44049],[-81.49042,30.72999],[-81.31371,30.03552],[-80.98,29.18],[-80.535585,28.47213],[-80.53,28.04],[-80.056539,26.88],[-80.088015,26.205765],[-80.13156,25.816775],[-80.38103,25.20616],[-80.68,25.08],[-81.17213,25.20126],[-81.33,25.64],[-81.71,25.87],[-82.24,26.73],[-82.70515,27.49504],[-82.85526,27.88624],[-82.65,28.55],[-82.93,29.1],[-83.70959,29.93656],[-84.1,30.09],[-85.10882,29.63615],[-85.28784,29.68612],[-85.7731,30.15261],[-86.4,30.4],[-87.53036,30.27433],[-88.41782,30.3849],[-89.18049,30.31598],[-89.593831,30.159994],[-89.413735,29.89419],[-89.43,29.48864],[-89.21767,29.29108],[-89.40823,29.15961],[-89.77928,29.30714],[-90.15463,29.11743],[-90.880225,29.148535],[-91.626785,29.677],[-92.49906,29.5523],[-93.22637,29.78375],[-93.84842,29.71363],[-94.69,29.48],[-95.60026,28.73863],[-96.59404,28.30748],[-97.14,27.83],[-97.37,27.38],[-97.38,26.69],[-97.33,26.21],[-97.14,25.87],[-97.53,25.84],[-98.24,26.06],[-99.02,26.37],[-99.3,26.84],[-99.52,27.54],[-100.11,28.11],[-100.45584,28.69612],[-100.9576,29.38071],[-101.6624,29.7793],[-102.48,29.76],[-103.11,28.97],[-103.94,29.27],[-104.45697,29.57196],[-104.70575,30.12173],[-105.03737,30.64402],[-105.63159,31.08383],[-106.1429,31.39995],[-106.50759,31.75452],[-108.24,31.754854],[-108.24194,31.34222],[-109.035,31.34194],[-111.02361,31.33472],[-113.30498,32.03914],[-114.815,32.52528],[-114.72139,32.72083],[-115.99135,32.61239],[-117.12776,32.53534],[-117.295938,33.046225],[-117.944,33.621236],[-118.410602,33.740909],[-118.519895,34.027782],[-119.081,34.078],[-119.438841,34.Line truncated
{"type":"Feature","properties":{"name":"Uzbekistan"},"geometry":{"type":"Polygon","coordinates":[[[66.518607,37.362784],[66.54615,37.974685],[65.215999,38.402695],[64.170223,38.892407],[63.518015,39.363257],[62.37426,40.053886],[61.882714,41.084857],[61.547179,41.26637],[60.465953,41.220327],[60.083341,41.425146],[59.976422,42.223082],[58.629011,42.751551],[57.78653,42.170553],[56.932215,41.826026],[57.096391,41.32231],[55.968191,41.308642],[55.928917,44.995858],[58.503127,45.586804],[58.689989,45.500014],[60.239972,44.784037],[61.05832,44.405817],[62.0133,43.504477],[63.185787,43.650075],[64.900824,43.728081],[66.098012,42.99766],[66.023392,41.994646],[66.510649,41.987644],[66.714047,41.168444],[67.985856,41.135991],[68.259896,40.662325],[68.632483,40.668681],[69.070027,41.384244],[70.388965,42.081308],[70.962315,42.266154],[71.259248,42.167711],[70.420022,41.519998],[71.157859,41.143587],[71.870115,41.3929],[73.055417,40.866033],[71.774875,40.145844],[71.014198,40.244366],[70.601407,40.218527],[70.45816,40.496495],[70.666622,40.960213],[69.329495,40.727824],[69.011633,40.086158],[68.536416,39.533453],[67.701429,39.580478],[67.44222,39.140144],[68.176025,38.901553],[68.392033,38.157025],[67.83,37.144994],[67.075782,37.356144],[66.518607,37.362784]]]},"id":"UZB"},
{"type":"Feature","properties":{"name":"Venezuela"},"geometry":{"type":"Polygon","coordinates":[[[-71.331584,11.776284],[-71.360006,11.539994],[-71.94705,11.423282],[-71.620868,10.96946],[-71.633064,10.446494],[-72.074174,9.865651],[-71.695644,9.072263],[-71.264559,9.137195],[-71.039999,9.859993],[-71.350084,10.211935],[-71.400623,10.968969],[-70.155299,11.375482],[-70.293843,11.846822],[-69.943245,12.162307],[-69.5843,11.459611],[-68.882999,11.443385],[-68.233271,10.885744],[-68.194127,10.554653],[-67.296249,10.545868],[-66.227864,10.648627],[-65.655238,10.200799],[-64.890452,10.077215],[-64.329479,10.389599],[-64.318007,10.641418],[-63.079322,10.701724],[-61.880946,10.715625],[-62.730119,10.420269],[-62.388512,9.948204],[-61.588767,9.873067],[-60.830597,9.38134],[-60.671252,8.580174],[-60.150096,8.602757],[-59.758285,8.367035],[-60.550588,7.779603],[-60.637973,7.415],[-60.295668,7.043911],[-60.543999,6.856584],[-61.159336,6.696077],[-61.139415,6.234297],[-61.410303,5.959068],[-60.733574,5.200277],[-60.601179,4.918098],[-60.966893,4.536468],[-62.08543,4.162124],[-62.804533,4.006965],[-63.093198,3.770571],[-63.888343,4.02053],[-64.628659,4.148481],[-64.816064,4.056445],[-64.368494,3.79721],[-64.408828,3.126786],[-64.269999,2.497006],[-63.422867,2.411068],[-63.368788,2.2009],[-64.083085,1.916369],[-64.199306,1.492855],[-64.611012,1.328731],[-65.354713,1.095282],[-65.548267,0.789254],[-66.325765,0.724452],[-66.876326,1.253361],[-67.181294,2.250638],[-67.447092,2.600281],[-67.809938,2.820655],[-67.303173,3.318454],[-67.337564,3.542342],[-67.621836,3.839482],[-67.823012,4.503937],[-67.744697,5.221129],[-67.521532,5.55687],[-67.34144,6.095468],[-67.695087,6.267318],[-68.265052,6.153268],[-68.985319,6.206805],[-69.38948,6.099861],[-70.093313,6.960376],[-70.674234,7.087785],[-71.960176,6.991615],[-72.198352,7.340431],[-72.444487,7.423785],[-72.479679,7.632506],[-72.360901,8.002638],[-72.439862,8.405275],[-72.660495,8.625288],[-72.78873,9.085027],[-73.304952,9.152],[-73.027604,9.73677],[-72.905286,10.450344],[-72.614658,10.821975],[-72.227575,11.108702],[-71.973922,11.608672],[-71.331584,11.776284]]]},"id":"VEN"},
{"type":"Feature","properties":{"name":"Vietnam"},"geometry":{"type":"Polygon","coordinates":[[[108.05018,21.55238],[106.715068,20.696851],[105.881682,19.75205],[105.662006,19.058165],[106.426817,18.004121],[107.361954,16.697457],[108.269495,16.079742],[108.877107,15.276691],[109.33527,13.426028],[109.200136,11.666859],[108.36613,11.008321],[107.220929,10.364484],[106.405113,9.53084],[105.158264,8.59976],[104.795185,9.241038],[105.076202,9.918491],[104.334335,10.486544],[105.199915,10.88931],[106.24967,10.961812],[105.810524,11.567615],[107.491403,12.337206],[107.614548,13.535531],[107.382727,14.202441],[107.564525,15.202173],[107.312706,15.908538],[106.556008,16.604284],[105.925762,17.485315],[105.094598,18.666975],[103.896532,19.265181],[104.183388,19.624668],[104.822574,19.886642],[104.435,20.758733],[103.203861,20.766562],[102.754896,21.675137],[102.170436,22.464753],[102.706992,22.708795],[103.504515,22.703757],[104.476858,22.81915],[105.329209,23.352063],[105.811247,22.976892],[106.725403,22.794268],[106.567273,22.218205],[107.04342,21.811899],[108.05018,21.55238]]]},"id":"VNM"},
{"type":"Feature","properties":{"name":"Vanuatu"},"geometry":{"type":"MultiPolygon","coordinates":[[[[167.844877,-16.466333],[167.515181,-16.59785],[167.180008,-16.159995],[167.216801,-15.891846],[167.844877,-16.466333]]],[[[167.107712,-14.93392],[167.270028,-15.740021],[167.001207,-15.614602],[166.793158,-15.668811],[166.649859,-15.392704],[166.629137,-14.626497],[167.107712,-14.93392]]]]},"id":"VUT"},
{"type":"Feature","properties":{"name":"West Bank"},"geometry":{"type":"Polygon","coordinates":[[[35.545665,32.393992],[35.545252,31.782505],[35.397561,31.489086],[34.927408,31.353435],[34.970507,31.616778],[35.225892,31.754341],[34.974641,31.866582],[35.18393,32.532511],[35.545665,32.393992]]]},"id":"PSE"},
{"type":"Feature","properties":{"name":"Yemen"},"geometry":{"type":"Polygon","coordinates":[[[53.108573,16.651051],[52.385206,16.382411],[52.191729,15.938433],[52.168165,15.59742],[51.172515,15.17525],[49.574576,14.708767],[48.679231,14.003202],[48.238947,13.94809],[47.938914,14.007233],[47.354454,13.59222],[46.717076,13.399699],[45.877593,13.347764],[45.62505,13.290946],[45.406459,13.026905],[45.144356,12.953938],[44.989533,12.699587],[44.494576,12.721653],[44.175113,12.58595],[43.482959,12.6368],[43.222871,13.22095],[43.251448,13.767584],[43.087944,14.06263],[42.892245,14.802249],[42.604873,15.213335],[42.805015,15.261963],[42.702438,15.718886],[42.823671,15.911742],[42.779332,16.347891],[43.218375,16.66689],[43.115798,17.08844],[43.380794,17.579987],[43.791519,17.319977],[44.062613,17.410359],[45.216651,17.433329],[45.399999,17.333335],[46.366659,17.233315],[46.749994,17.283338],[47.000005,16.949999],[47.466695,17.116682],[48.183344,18.166669],[49.116672,18.616668],[52.00001,19.000003],[52.782184,17.349742],[53.108573,16.651051]]]},"id":"YEM"},
{"type":"Feature","properties":{"name":"South Africa"},"geometry":{"type":"Polygon","coordinates":[[[31.521001,-29.257387],[31.325561,-29.401978],[30.901763,-29.909957],[30.622813,-30.423776],[30.055716,-31.140269],[28.925553,-32.172041],[28.219756,-32.771953],[27.464608,-33.226964],[26.419452,-33.61495],[25.909664,-33.66704],[25.780628,-33.944646],[25.172862,-33.796851],[24.677853,-33.987176],[23.594043,-33.794474],[22.988189,-33.916431],[22.574157,-33.864083],[21.542799,-34.258839],[20.689053,-34.417175],[20.071261,-34.795137],[19.616405,-34.819166],[19.193278,-34.462599],[18.855315,-34.444306],[18.424643,-33.997873],[18.377411,-34.136521],[18.244499,-33.867752],[18.25008,-33.281431],[17.92519,-32.611291],[18.24791,-32.429131],[18.221762,-31.661633],[17.566918,-30.725721],[17.064416,-29.878641],[17.062918,-29.875954],[16.344977,-28.576705],[16.824017,-28.082162],[17.218929,-28.355943],[17.387497,-28.783514],[17.836152,-28.856378],[18.464899,-29.045462],[19.002127,-28.972443],[19.894734,-28.461105],[19.895768,-24.76779],[20.165726,-24.917962],[20.758609,-25.868136],[20.66647,-26.477453],[20.889609,-26.828543],[21.605896,-26.726534],[22.105969,-26.280256],[22.579532,-25.979448],[22.824271,-25.500459],[23.312097,-25.26869],[23.73357,-25.390129],[24.211267,-25.670216],[25.025171,-25.71967],[25.664666,-25.486816],[25.765849,-25.174845],[25.941652,-24.696373],[26.485753,-24.616327],[26.786407,-24.240691],[27.11941,-23.574323],[28.017236,-22.827754],[29.432188,-22.091313],[29.839037,-22.102216],[30.322883,-22.271612],[30.659865,-22.151567],[31.191409,-22.25151],[31.670398,-23.658969],[31.930589,-24.369417],[31.752408,-25.484284],[31.837778,-25.843332],[31.333158,-25.660191],[31.04408,-25.731452],[30.949667,-26.022649],[30.676609,-26.398078],[30.685962,-26.743845],[31.282773,-27.285879],[31.86806,-27.177927],[32.071665,-26.73382],[32.83012,-26.742192],[32.580265,-27.470158],[32.462133,-28.301011],[32.203389,-28.752405],[31.521001,-29.257387]],[[28.978263,-28.955597],[28.5417,-28.647502],[28.074338,-28.851469],[27.532511,-29.242711],[26.999262,-29.875954],[27.749397,-30.645106],[28.107205,-30.545732],[28.291069,-30.226217],[28.8484,-30.070051],[29.018415,-29.743766],[29.325166,-29.257387],[28.978263,-28.955597]]]},"id":"ZAF"},
{"type":"Feature","properties":{"name":"Zambia"},"geometry":{"type":"Polygon","coordinates":[[[32.759375,-9.230599],[33.231388,-9.676722],[33.485688,-10.525559],[33.31531,-10.79655],[33.114289,-11.607198],[33.306422,-12.435778],[32.991764,-12.783871],[32.688165,-13.712858],[33.214025,-13.97186],[30.179481,-14.796099],[30.274256,-15.507787],[29.516834,-15.644678],[28.947463,-16.043051],[28.825869,-16.389749],[28.467906,-16.4684],[27.598243,-17.290831],[27.044427,-17.938026],[26.706773,-17.961229],[26.381935,-17.846042],[25.264226,-17.73654],[25.084443,-17.661816],[25.07695,-17.578823],[24.682349,-17.353411],[24.033862,-17.295843],[23.215048,-17.523116],[22.562478,-16.898451],[21.887843,-16.08031],[21.933886,-12.898437],[24.016137,-12.911046],[23.930922,-12.565848],[24.079905,-12.191297],[23.904154,-11.722282],[24.017894,-11.237298],[23.912215,-10.926826],[24.257155,-10.951993],[24.314516,-11.262826],[24.78317,-11.238694],[25.418118,-11.330936],[25.75231,-11.784965],[26.553088,-11.92444],[27.16442,-11.608748],[27.388799,-12.132747],[28.155109,-12.272481],[28.523562,-12.698604],[28.934286,-13.248958],[29.699614,-13.257227],[29.616001,-12.178895],[29.341548,-12.360744],[28.642417,-11.971569],[28.372253,-11.793647],[28.49607,-10.789884],[28.673682,-9.605925],[28.449871,-9.164918],[28.734867,-8.526559],[29.002912,-8.407032],[30.346086,-8.238257],[30.740015,-8.340007],[31.157751,-8.594579],[31.556348,-8.762049],[32.191865,-8.930359],[32.759375,-9.230599]]]},"id":"ZMB"},
{"type":"Feature","properties":{"name":"Zimbabwe"},"geometry":{"type":"Polygon","coordinates":[[[31.191409,-22.25151],[30.659865,-22.151567],[30.322883,-22.271612],[29.839037,-22.102216],[29.432188,-22.091313],[28.794656,-21.639454],[28.02137,-21.485975],[27.727228,-20.851802],[27.724747,-20.499059],[27.296505,-20.39152],[26.164791,-19.293086],[25.850391,-18.714413],[25.649163,-18.536026],[25.264226,-17.73654],[26.381935,-17.846042],[26.706773,-17.961229],[27.044427,-17.938026],[27.598243,-17.290831],[28.467906,-16.4684],[28.825869,-16.389749],[28.947463,-16.043051],[29.516834,-15.644678],[30.274256,-15.507787],[30.338955,-15.880839],[31.173064,-15.860944],[31.636498,-16.07199],[31.852041,-16.319417],[32.328239,-16.392074],[32.847639,-16.713398],[32.849861,-17.979057],[32.654886,-18.67209],[32.611994,-19.419383],[32.772708,-19.715592],[32.659743,-20.30429],[32.508693,-20.395292],[32.244988,-21.116489],[31.191409,-22.25151]]]},"id":"ZWE"}
]}
@@ -0,0 +1,35 @@
// Version 2.32.1 globe.gl - https://github.com/vasturiano/globe.gl
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Globe=e()}(this,(function(){"use strict";function t(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function e(e){for(var r=1;r<arguments.length;r++){var i=null!=arguments[r]?arguments[r]:{};r%2?t(Object(i),!0).forEach((function(t){n(e,t,i[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):t(Object(i)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))}))}return e}function n(t,e,n){return(e=function(t){var e=function(t,e){if("object"!=typeof t||null===t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var r=n.call(t,e||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function r(t,e){if(null==t)return{};var n,r,i=function(t,e){if(null==t)return{};var n,r,i={},a=Object.keys(t);for(r=0;r<a.length;r++)n=a[r],e.indexOf(n)>=0||(i[n]=t[n]);return i}(t,e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);for(r=0;r<a.length;r++)n=a[r],e.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(i[n]=t[n])}return i}function i(t){return function(t){if(Array.isArray(t))return a(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(!t)return;if("string"==typeof t)return a(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return a(t,e)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n<e;n++)r[n]=t[n];return r}!function(t,e){void 0===e&&(e={});var n=e.insertAt;if(t&&"undefined"!=typeof document){var r=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===n&&r.firstChild?r.insertBefore(i,r.firstChild):r.appendChild(i),i.styleSheet?i.styleSheet.cssText=t:i.appendChild(document.createTextNode(t))}}(".scene-container .clickable {\n cursor: pointer;\n}");const o="157",s={LEFT:0,MIDDLE:1,RIGHT:2,ROTATE:0,DOLLY:1,PAN:2},A=0,l=1,c=2,u=3,h=1,f=2,d=3,p=0,m=1,g=100,v=0,_=1,y=2,x=0,b=1,M=2,w=3,E=4,S=5,T=301,C=302,R=306,P=1e3,L=1001,D=1002,O=1003,q=1005,I=1006,U=1008,N=1009,B=1012,k=1013,F=1014,z=1015,G=1016,H=1020,V=1023,j=1026,W=1027,X=33776,Q=33777,Y=33778,Z=33779,K=36492,J=3001,$="",tt="srgb",et="srgb-linear",nt="display-p3",rt="display-p3-linear",it="linear",at="srgb",ot="rec709",st="p3",At=7680,lt=35044,ct="300 es",ut=1035,ht=2e3,ft=2001;class dt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,r=n.length;e<r;e++)n[e].call(this,t);t.target=null}}}const pt=["00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f","20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f","30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f","40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f","50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f","60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f","70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f","80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f","a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af","b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf","c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf","d0","d1","d2","d3",Line truncated
// <http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2)
// Analyze the 2 colors and returns the color contrast defined by (WCAG Version 2)
Rf.readability=function(t,e){var n=Rf(t),r=Rf(e);return(Math.max(n.getLuminance(),r.getLuminance())+.05)/(Math.min(n.getLuminance(),r.getLuminance())+.05)},Rf.isReadable=function(t,e,n){var r,i,a=Rf.readability(t,e);switch(i=!1,(r=function(t){var e,n;e=((t=t||{level:"AA",size:"small"}).level||"AA").toUpperCase(),n=(t.size||"small").toLowerCase(),"AA"!==e&&"AAA"!==e&&(e="AA");"small"!==n&&"large"!==n&&(n="small");return{level:e,size:n}}(n)).level+r.size){case"AAsmall":case"AAAlarge":i=a>=4.5;break;case"AAlarge":i=a>=3;break;case"AAAsmall":i=a>=7}return i},Rf.mostReadable=function(t,e,n){var r,i,a,o,s=null,A=0;i=(n=n||{}).includeFallbackColors,a=n.level,o=n.size;for(var l=0;l<e.length;l++)(r=Rf.readability(t,e[l]))>A&&(A=r,s=Rf(e[l]));return Rf.isReadable(t,s,{level:a,size:o})||!i?s:(n.includeFallbackColors=!1,Rf.mostReadable(t,["#fff","#000"],n))};var Wf=Rf.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},Xf=Rf.hexNames=function(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[t[n]]=n);return e}(Wf);function Qf(t){return t=parseFloat(t),(isNaN(t)||t<0||t>1)&&(t=1),t}function Yf(t,e){(function(t){return"string"==typeof t&&-1!=t.indexOf(".")&&1===parseFloat(t)})(t)&&(t="100%");var n=function(t){return"string"==typeof t&&-1!=t.indexOf("%")}(t);return t=Math.min(e,Math.max(0,parseFloat(t))),n&&(t=parseInt(t*e,10)/100),Math.abs(t-e)<1e-6?1:t%e/parseFloat(e)}function Zf(t){return Math.min(1,Math.max(0,t))}function Kf(t){return parseInt(t,16)}function Jf(t){return 1==t.length?"0"+t:""+t}function $f(t){return t<=1&&(t=100*t+"%"),t}function td(t){return Math.round(255*parseFloat(t)).toString(16)}function ed(t){return Kf(t)/255}var nd,rd,id,ad=(rd="[\\s|\\(]+("+(nd="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+nd+")[,|\\s]+("+nd+")\\s*\\)?",id="[\\s|\\(]+("+nd+")[,|\\s]+("+nd+")[,|\\s]+("+nd+")[,|\\s]+("+nd+")\\s*\\)?",{CSS_UNIT:new RegExp(nd),rgb:new RegExp("rgb"+rd),rgba:new RegExp("rgba"+id),hsl:new RegExp("hsl"+rd),hsla:new RegExp("hsla"+id),hsv:new RegExp("hsv"+rd),hsva:new RegExp("hsva"+id),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]Line truncated
/*
* Copyright 2018-2019, 2022 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/Dm[Lm]="Resolution argument was outside of acceptable range",Dm[5]="Cell argument was not valid",Dm[6]="Directed edge argument was not valid",Dm[7]="Undirected edge argument was not valid",Dm[8]="Vertex argument was not valid",Dm[9]="Pentagon distortion was encountered",Dm[10]="Duplicate input",Dm[11]="Cell arguments were not neighbors",Dm[12]="Cell arguments had incompatible resolutions",Dm[13]="Memory allocation failed",Dm[14]="Bounds of provided memory were insufficient",Dm[15]="Mode or flags argument was not valid";var Om=1001,qm=1002,Im={1e3:"Unknown unit"};Im[Om]="Array length out of bounds",Im[qm]="Got unexpected null value for H3 index";var Um="Unknown error";function Nm(t,e,n){var r=n&&"value"in n,i=new Error((t[e]||Um)+" (code: "+e+(r?", value: "+n.value:"")+")");return i.code=e,i}function Bm(t,e){return Nm(Dm,t,2===arguments.length?{value:e}:{})}function km(t,e){return Nm(Im,t,2===arguments.length?{value:e}:{})}function Fm(t){if(0!==t)throw Bm(t)}
/*
* Copyright 2018-2019, 2022 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/var zm={};Pm.forEach((function(t){zm[t[0]]=Cm.cwrap.apply(Cm,t)}));var Gm=16,Hm=4,Vm=8,jm=zm.sizeOfH3Index(),Wm=zm.sizeOfLatLng(),Xm=zm.sizeOfCellBoundary(),Qm=zm.sizeOfGeoPolygon(),Ym=zm.sizeOfGeoLoop();zm.sizeOfLinkedGeoPolygon(),zm.sizeOfCoordIJ();var Zm=Math.pow(2,32)-1;var Km=/[^0-9a-fA-F]/;function Jm(t){if(Array.isArray(t)&&2===t.length&&Number.isInteger(t[0])&&Number.isInteger(t[1]))return t;if("string"!=typeof t||Km.test(t))return[0,0];var e=parseInt(t.substring(0,t.length-8),Gm);return[parseInt(t.substring(t.length-8),Gm),e]}function $m(t){if(t>=0)return t.toString(Gm);var e=tg(8,(t&=2147483647).toString(Gm));return e=(parseInt(e[0],Gm)+8).toString(Gm)+e.substring(1)}function tg(t,e){for(var n=t-e.length,r="",i=0;i<n;i++)r+="0";return r+=e}function eg(t,e,n){for(var r=t.length,i=Cm._calloc(r,Wm),a=n?1:0,o=n?0:1,s=0;s<2*r;s+=2)Cm.HEAPF64.set([t[s/2][a],t[s/2][o]].map(cg),i/Vm+s);return Cm.HEAPU32.set([r,i],e/Hm),e}function ng(t,e){void 0===e&&(e=0);var n=Cm.getValue(t+jm*e,"i32"),r=Cm.getValue(t+jm*e+Hm,"i32");return r?function(t,e){return $m(e)+tg(8,$m(t))}(n,r):null}function rg(t){return 180*Cm.getValue(t,"double")/Math.PI}function ig(t){return[rg(t),rg(t+Vm)]}function ag(t){return[rg(t+Vm),rg(t)]}function og(t,e,n){var r=Cm._malloc(Wm);Cm.HEAPF64.set([t,e].map(cg),r/Vm);var i=Cm._malloc(jm);try{return Fm(zm.latLngToCell(r,n,i)),function(t){if(!t)throw km(qm);return t}(ng(i))}finally{Cm._free(i),Cm._free(r)}}function sg(t){var e=Cm._malloc(Wm),n=Jm(t),r=n[0],i=n[1];try{return Fm(zm.cellToLatLng(r,i,e)),ig(e)}finally{Cm._free(e)}}function Ag(t,e){var n=Cm._malloc(Xm),r=Jm(t),i=r[0],a=r[1];try{return Fm(zm.cellToBoundary(i,a,n)),function(t,e,n){for(var r=Cm.getValue(t,"i32"),i=t+Vm,a=[],o=e?ag:ig,s=0;s<2*r;s+=2)a.push(o(i+Vm*s));return n&&a.push(a[0]),a}(n,e,e)}finally{Cm._free(n)}}function lg(t,e,n){if(function(t){if("number"!=typeof t||t<0||t>15||Math.floor(t)!==t)throw Bm(Lm,t)}(e),n=Boolean(n),0===t.length||0===t[0].length)return[];var r,i=function(t,e){var n,r=t.length-1,i=Cm._calloc(Qm),a=0+Ym,o=a+Hm;if(eg(t[0],i+0,e),r>0){n=Cm._calloc(r,Ym);for(var s=0;s<r;s++)eg(t[s+1],n+Ym*s,e)}return Cm.setValue(i+a,r,"i32"),Cm.setValue(i+o,n,"i32"),i}("number"==typeof t[0][0]?[t]:t,n),a=Cm._malloc(8);try{Fm(zm.maxPolygonToCellsSize(i,e,0,a));var o=function(t){if(t>Zm)throw km(Om,t);return t}((r=a,zm.readInt64AsDoubleFromPointer(r))),s=Cm._calloc(o,jm);try{return Fm(zm.polygonToCells(i,e,0,s)),function(t,e){for(var n=[],r=0;r<e;r++){var i=ng(t,r);null!==i&&n.push(i)}return n}(s,o)}finally{Cm._free(s)}}finally{Cm._free(a),function(t){var e=0+Ym,n=e+Hm,r=Hm;Cm._free(Cm.getValue(t+0+r,"i8*"));var i=Cm.getValue(t+e,"i32");if(i>0){for(var a=Cm.getValue(t+n,"i32"),o=0;o<i;o++)Cm._free(Cm.getValue(a+Ym*o+r,"i8*"));Cm._free(a)}Cm._free(t)}(i)}}function cg(t){return t*Math.PI/180}var ug=hg;function hg(t,e,n,r){this.x="number"==typeof t?t:0,this.y="number"==typeof e?e:0,this.z="number"==typeof n?n:0,this.half="number"==typeof r?r:0}hg.prototype.contains=function(t,e,n){var r=this.half;return this.x-r<=t&&t<this.x+r&&this.y-r<=e&&e<this.y+r&&this.z-r<=n&&n<this.z+r};var fg=ug,dg=pg;function pg(t){this.bounds=t,this.q0=null,this.q1=null,this.q2=null,this.q3=null,this.q4=null,this.q5=null,this.q6=null,this.q7=null,this.items=null}pg.prototype.subdivide=function(){var t=this.bounds,e=t.half/2;this.q0=new pg(new fg(t.x-e,t.y-e,t.z-e,e)),this.q1=new pg(new fg(t.x+e,t.y-e,t.z-e,e)),this.q2=new pg(new fg(t.x-e,t.y+e,t.z-e,e)),this.q3=new pg(new fg(t.x+e,t.y+e,t.z-e,e)),this.q4=new pg(new fg(t.x-e,t.y-e,t.z+e,e)),this.q5=new pg(new fg(t.x+e,t.y-e,t.z+e,e)),this.q6=new pg(new fg(t.x-e,t.y+e,t.z+e,e)),this.q7=new pg(new fg(t.x+e,t.y+e,t.z+e,e))},pg.prototype.insert=function(t,e,n){if(null===this.q0){if(null===this.items?this.items=[t]:this.items.push(t),this.items.length>=4&&n<16){this.subdivide();for(var r=0;r<this.items.length;++r)this.insert(this.items[r],e,n+1);this.items=null}}else{var i=e[t],a=e[t+1],o=e[t+2],s=this.bounds,A=0;i>s.x&&(A+=1),a>s.y&&(A+=2),o>s.z&&(A+=4);var l=function(t,e){if(0===e)return t.q0;if(1===e)return t.q1;if(2===e)return t.q2;if(3===e)return t.q3;if(4===e)return t.q4;if(5===e)return t.q5;if(6===e)return t.q6;if(7===e)return t.q7}(this,A);l.insert(t,e,n+1)}},pg.prototype.query=function(t,e,n,r){if(n(this.bounds)){var i=this.items,a="function"==typeof r;if(i)for(var o=0;o<i.length;++o){var s=i[o];a?r(e[s],e[s+1],e[s+2])&&t.push(s):t.push(s)}this.q0&&(this.q0.query(t,e,n,r),this.q1.query(t,e,n,r),this.q2.query(t,e,n,r),this.q3.query(t,e,n,r),this.q4.query(t,e,n,r),this.q5.query(t,e,n,r),this.q6.query(t,e,n,r),this.q7.query(t,e,n,r))}};var mg=function(t,e,n,r){var i=0,a=0,o=(r=r||{}).step||1,s=r.maxTimeMS||8,A=r.probeElements||5e3;setTimeout((function r(){var l=Math.min(t.length,i+A),c=i,u=new Date;for(c=i;c<l;c+=o)e(t[c],c,t);c<t.length?(a+=new Date-u,i=c,A=Math.round(i*s/a),setTimeout(r,0)):n(t)}),0)};var gg=ug,vg=dg,_g=new gg,yg=mg,xg=function(t){var e,n,r=[],i={initAsync:function(t,r){o(t);Line truncated
@@ -0,0 +1 @@
.maplibregl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-highlight-color:rgb(0 0 0/0)}.maplibregl-canvas{left:0;position:absolute;top:0}.maplibregl-map:fullscreen{height:100%;width:100%}.maplibregl-ctrl-group button.maplibregl-ctrl-compass{touch-action:none}.maplibregl-canvas-container.maplibregl-interactive,.maplibregl-ctrl-group button.maplibregl-ctrl-compass{cursor:grab;-webkit-user-select:none;-moz-user-select:none;user-select:none}.maplibregl-canvas-container.maplibregl-interactive.maplibregl-track-pointer{cursor:pointer}.maplibregl-canvas-container.maplibregl-interactive:active,.maplibregl-ctrl-group button.maplibregl-ctrl-compass:active{cursor:grabbing}.maplibregl-canvas-container.maplibregl-touch-zoom-rotate,.maplibregl-canvas-container.maplibregl-touch-zoom-rotate .maplibregl-canvas{touch-action:pan-x pan-y}.maplibregl-canvas-container.maplibregl-touch-drag-pan,.maplibregl-canvas-container.maplibregl-touch-drag-pan .maplibregl-canvas{touch-action:pinch-zoom}.maplibregl-canvas-container.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan,.maplibregl-canvas-container.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan .maplibregl-canvas{touch-action:none}.maplibregl-canvas-container.maplibregl-touch-drag-pan.maplibregl-cooperative-gestures,.maplibregl-canvas-container.maplibregl-touch-drag-pan.maplibregl-cooperative-gestures .maplibregl-canvas{touch-action:pan-x pan-y}.maplibregl-ctrl-bottom-left,.maplibregl-ctrl-bottom-right,.maplibregl-ctrl-top-left,.maplibregl-ctrl-top-right{pointer-events:none;position:absolute;z-index:2}.maplibregl-ctrl-top-left{left:0;top:0}.maplibregl-ctrl-top-right{right:0;top:0}.maplibregl-ctrl-bottom-left{bottom:0;left:0}.maplibregl-ctrl-bottom-right{bottom:0;right:0}.maplibregl-ctrl{clear:both;pointer-events:auto;transform:translate(0)}.maplibregl-ctrl-top-left .maplibregl-ctrl{float:left;margin:10px 0 0 10px}.maplibregl-ctrl-top-right .maplibregl-ctrl{float:right;margin:10px 10px 0 0}.maplibregl-ctrl-bottom-left .maplibregl-ctrl{float:left;margin:0 0 10px 10px}.maplibregl-ctrl-bottom-right .maplibregl-ctrl{float:right;margin:0 10px 10px 0}.maplibregl-ctrl-group{background:#fff;border-radius:4px}.maplibregl-ctrl-group:not(:empty){box-shadow:0 0 0 2px rgba(0,0,0,.1)}@media (forced-colors:active){.maplibregl-ctrl-group:not(:empty){box-shadow:0 0 0 2px ButtonText}}.maplibregl-ctrl-group button{background-color:transparent;border:0;box-sizing:border-box;cursor:pointer;display:block;height:29px;outline:none;padding:0;width:29px}.maplibregl-ctrl-group button+button{border-top:1px solid #ddd}.maplibregl-ctrl button .maplibregl-ctrl-icon{background-position:50%;background-repeat:no-repeat;display:block;height:100%;width:100%}@media (forced-colors:active){.maplibregl-ctrl-icon{background-color:transparent}.maplibregl-ctrl-group button+button{border-top:1px solid ButtonText}}.maplibregl-ctrl button::-moz-focus-inner{border:0;padding:0}.maplibregl-ctrl-attrib-button:focus,.maplibregl-ctrl-group button:focus{box-shadow:0 0 2px 2px #0096ff}.maplibregl-ctrl button:disabled{cursor:not-allowed}.maplibregl-ctrl button:disabled .maplibregl-ctrl-icon{opacity:.25}.maplibregl-ctrl button:not(:disabled):hover{background-color:rgb(0 0 0/5%)}.maplibregl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.maplibregl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}.maplibregl-ctrl-group button:focus:first-child{border-radius:4px 4px 0 0}.maplibregl-ctrl-group button:focus:last-child{border-radius:0 0 4px 4px}.maplibregl-ctrl-group button:focus:only-child{border-radius:inherit}.maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13z'/%3E%3C/svg%3E")}.maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5'/%3E%3C/svg%3E")}@media (forced-colors:active){.maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13z'/%3E%3C/svg%3E")}.maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23fff' viewLine truncated
@@ -0,0 +1,59 @@
/**
* MapLibre GL JS
* @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v4.7.1/LICENSE.txt
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.maplibregl = factory());
})(this, (function () { 'use strict';
/* eslint-disable */
var maplibregl = {};
var modules = {};
function define(moduleName, _dependencies, moduleFactory) {
modules[moduleName] = moduleFactory;
// to get the list of modules see generated dist/maplibre-gl-dev.js file (look for `define(` calls)
if (moduleName !== 'index') {
return;
}
// we assume that when an index module is initializing then other modules are loaded already
var workerBundleString = 'var sharedModule = {}; (' + modules.shared + ')(sharedModule); (' + modules.worker + ')(sharedModule);'
var sharedModule = {};
// the order of arguments of a module factory depends on rollup (it decides who is whose dependency)
// to check the correct order, see dist/maplibre-gl-dev.js file (look for `define(` calls)
// we assume that for our 3 chunks it will generate 3 modules and their order is predefined like the following
modules.shared(sharedModule);
modules.index(maplibregl, sharedModule);
if (typeof window !== 'undefined') {
maplibregl.setWorkerUrl(window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })));
}
return maplibregl;
};
define("shared",["exports"],(function(t){"use strict";function e(t,e,r,n){return new(r||(r=Promise))((function(i,s){function a(t){try{l(n.next(t));}catch(t){s(t);}}function o(t){try{l(n.throw(t));}catch(t){s(t);}}function l(t){var e;t.done?i(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e);}))).then(a,o);}l((n=n.apply(t,e||[])).next());}))}function r(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}"function"==typeof SuppressedError&&SuppressedError;var n=i;function i(t,e){this.x=t,this.y=e;}i.prototype={clone:function(){return new i(this.x,this.y)},add:function(t){return this.clone()._add(t)},sub:function(t){return this.clone()._sub(t)},multByPoint:function(t){return this.clone()._multByPoint(t)},divByPoint:function(t){return this.clone()._divByPoint(t)},mult:function(t){return this.clone()._mult(t)},div:function(t){return this.clone()._div(t)},rotate:function(t){return this.clone()._rotate(t)},rotateAround:function(t,e){return this.clone()._rotateAround(t,e)},matMult:function(t){return this.clone()._matMult(t)},unit:function(){return this.clone()._unit()},perp:function(){return this.clone()._perp()},round:function(){return this.clone()._round()},mag:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},equals:function(t){return this.x===t.x&&this.y===t.y},dist:function(t){return Math.sqrt(this.distSqr(t))},distSqr:function(t){var e=t.x-this.x,r=t.y-this.y;return e*e+r*r},angle:function(){return Math.atan2(this.y,this.x)},angleTo:function(t){return Math.atan2(this.y-t.y,this.x-t.x)},angleWith:function(t){return this.angleWithSep(t.x,t.y)},angleWithSep:function(t,e){return Math.atan2(this.x*e-this.y*t,this.x*t+this.y*e)},_matMult:function(t){var e=t[2]*this.x+t[3]*this.y;return this.x=t[0]*this.x+t[1]*this.y,this.y=e,this},_add:function(t){return this.x+=t.x,this.y+=t.y,this},_sub:function(t){return this.x-=t.x,this.y-=t.y,this},_mult:function(t){return this.x*=t,this.y*=t,this},_div:function(t){return this.x/=t,this.y/=t,this},_multByPoint:function(t){return this.x*=t.x,this.y*=t.y,this},_divByPoint:function(t){return this.x/=t.x,this.y/=t.y,this},_unit:function(){return this._div(this.mag()),this},_perp:function(){var t=this.y;return this.y=this.x,this.x=-t,this},_rotate:function(t){var e=Math.cos(t),r=Math.sin(t),n=r*this.x+e*this.y;return this.x=e*this.x-r*this.y,this.y=n,this},_rotateAround:function(t,e){var r=Math.cos(t),n=Math.sin(t),i=e.y+n*(this.x-e.x)+r*(this.y-e.y);return this.x=e.x+r*(this.x-e.x)-n*(this.y-e.y),this.y=i,this},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}},i.convert=function(t){return t instanceof i?t:Array.isArray(t)?new i(t[0],t[1]):t};var s=r(n),a=o;function o(t,e,r,n){this.cx=3*t,this.bx=3*(r-t)-this.cx,this.ax=1-this.cx-this.bx,this.cy=3*e,this.by=3*(n-e)-this.cy,this.ay=1-this.cy-this.by,this.p1x=t,this.p1y=e,this.p2x=r,this.p2y=n;}o.prototype={sampleCurveX:function(t){return ((this.ax*t+this.bx)*t+this.cx)*t},sampleCurveY:function(t){return ((this.ay*t+this.by)*t+this.cy)*t},sampleCurveDerivativeX:function(t){return (3*this.ax*t+2*this.bx)*t+this.cx},solveCurveX:function(t,e){if(void 0===e&&(e=1e-6),t<0)return 0;if(t>1)return 1;for(var r=t,n=0;n<8;n++){var i=this.sampleCurveX(r)-t;if(Math.abs(i)<e)return r;var s=this.sampleCurveDerivativeX(r);if(Math.abs(s)<1e-6)break;r-=i/s;}var a=0,o=1;for(r=t,n=0;n<20&&(i=this.sampleCurveX(r),!(Math.abs(i-t)<e));n++)t>i?a=r:o=r,r=.5*(o-a)+a;return r},solve:function(t,e){return this.sampleCurveY(this.solveCurveX(t,e))}};var l=r(a);let u,c;function h(){return null==u&&(u="undefined"!=typeof OffscreenCanvas&&new OffscreenCanvas(1,1).getContext("2d")&&"function"==typeof createImageBitmap),u}function p(){if(null==c&&(c=!1,h())){const t=5,e=new OffscreenCanvas(t,t).getContext("2d",{willReadFrequently:!0});if(e){for(let r=0;r<t*t;r++){const n=4*r;e.fillStyle=`rgb(${n},${n+1},${n+2})`,e.fillRect(r%t,Math.floor(r/t),1,1);}const r=e.getImageData(0,0,t,t).data;for(let e=0;e<t*t*4;e++)if(e%4!=3&&r[e]!==e){c=!0;break}}}return c||!1}function f(t,e,r,n){const i=new l(t,e,r,n);return t=>i.solve(t)}const d=f(.25,.1,.25,1);function y(t,e,r){return Math.min(r,Math.max(e,t))}function m(t,e,r){const n=r-e,i=((t-e)%n+n)%n+e;return i===e?r:i}function g(t,...e){for(const r of e)for(const e in r)t[e]=r[e];return t}let x=1;function v(t,e,r){const n={};for(const r in t)n[r]=e.call(this,t[r],r,t);return n}function b(t,e,r){const n={};for(const r in t)e.call(this,t[r],r,t)&&(n[r]=t[r]);return n}function w(t){return Array.isArray(t)?t.map(w):"object"==typeof t&&t?v(t,w):t}const _={};function A(t){_[t]||("undefined"!=typeof console&&console.warn(t),_[t]=!0);}function S(t,e,r){return (r.y-t.y)*(e.x-t.x)>(e.y-t.y)*(r.x-t.x)}function k(t){return "undefined"!=typeof WorkerGlobalScope&&void 0!==t&&t instanceof WorkerGlobalScope}let M=null;function I(t){return "undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap}const z="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCLine truncated
define("worker",["./shared"],(function(e){"use strict";class t{constructor(e){this.keyCache={},e&&this.replace(e);}replace(e){this._layerConfigs={},this._layers={},this.update(e,[]);}update(t,o){for(const o of t){this._layerConfigs[o.id]=o;const t=this._layers[o.id]=e.aA(o);t._featureFilter=e.a7(t.filter),this.keyCache[o.id]&&delete this.keyCache[o.id];}for(const e of o)delete this.keyCache[e],delete this._layerConfigs[e],delete this._layers[e];this.familiesBySource={};const i=e.bk(Object.values(this._layerConfigs),this.keyCache);for(const e of i){const t=e.map((e=>this._layers[e.id])),o=t[0];if("none"===o.visibility)continue;const i=o.source||"";let s=this.familiesBySource[i];s||(s=this.familiesBySource[i]={});const r=o.sourceLayer||"_geojsonTileLayer";let n=s[r];n||(n=s[r]=[]),n.push(t);}}}class o{constructor(t){const o={},i=[];for(const e in t){const s=t[e],r=o[e]={};for(const e in s){const t=s[+e];if(!t||0===t.bitmap.width||0===t.bitmap.height)continue;const o={x:0,y:0,w:t.bitmap.width+2,h:t.bitmap.height+2};i.push(o),r[e]={rect:o,metrics:t.metrics};}}const{w:s,h:r}=e.p(i),n=new e.o({width:s||1,height:r||1});for(const i in t){const s=t[i];for(const t in s){const r=s[+t];if(!r||0===r.bitmap.width||0===r.bitmap.height)continue;const a=o[i][t].rect;e.o.copy(r.bitmap,n,{x:0,y:0},{x:a.x+1,y:a.y+1},r.bitmap);}}this.image=n,this.positions=o;}}e.bl("GlyphAtlas",o);class i{constructor(t){this.tileID=new e.S(t.tileID.overscaledZ,t.tileID.wrap,t.tileID.canonical.z,t.tileID.canonical.x,t.tileID.canonical.y),this.uid=t.uid,this.zoom=t.zoom,this.pixelRatio=t.pixelRatio,this.tileSize=t.tileSize,this.source=t.source,this.overscaling=this.tileID.overscaleFactor(),this.showCollisionBoxes=t.showCollisionBoxes,this.collectResourceTiming=!!t.collectResourceTiming,this.returnDependencies=!!t.returnDependencies,this.promoteId=t.promoteId,this.inFlightDependencies=[];}parse(t,i,r,n){return e._(this,void 0,void 0,(function*(){this.status="parsing",this.data=t,this.collisionBoxArray=new e.a5;const a=new e.bm(Object.keys(t.layers).sort()),l=new e.bn(this.tileID,this.promoteId);l.bucketLayerIDs=[];const c={},u={featureIndex:l,iconDependencies:{},patternDependencies:{},glyphDependencies:{},availableImages:r},h=i.familiesBySource[this.source];for(const o in h){const i=t.layers[o];if(!i)continue;1===i.version&&e.w(`Vector tile source "${this.source}" layer "${o}" does not use vector tile spec v2 and therefore may have some rendering errors.`);const n=a.encode(o),d=[];for(let e=0;e<i.length;e++){const t=i.feature(e),s=l.getId(t,o);d.push({feature:t,id:s,index:e,sourceLayerIndex:n});}for(const t of h[o]){const o=t[0];o.source!==this.source&&e.w(`layer.source = ${o.source} does not equal this.source = ${this.source}`),o.minzoom&&this.zoom<Math.floor(o.minzoom)||o.maxzoom&&this.zoom>=o.maxzoom||"none"!==o.visibility&&(s(t,this.zoom,r),(c[o.id]=o.createBucket({index:l.bucketLayerIDs.length,layers:t,zoom:this.zoom,pixelRatio:this.pixelRatio,overscaling:this.overscaling,collisionBoxArray:this.collisionBoxArray,sourceLayerIndex:n,sourceID:this.source})).populate(d,u,this.tileID.canonical),l.bucketLayerIDs.push(t.map((e=>e.id))));}}const d=e.aF(u.glyphDependencies,(e=>Object.keys(e).map(Number)));this.inFlightDependencies.forEach((e=>null==e?void 0:e.abort())),this.inFlightDependencies=[];let f=Promise.resolve({});if(Object.keys(d).length){const e=new AbortController;this.inFlightDependencies.push(e),f=n.sendAsync({type:"GG",data:{stacks:d,source:this.source,tileID:this.tileID,type:"glyphs"}},e);}const g=Object.keys(u.iconDependencies);let p=Promise.resolve({});if(g.length){const e=new AbortController;this.inFlightDependencies.push(e),p=n.sendAsync({type:"GI",data:{icons:g,source:this.source,tileID:this.tileID,type:"icons"}},e);}const m=Object.keys(u.patternDependencies);let y=Promise.resolve({});if(m.length){const e=new AbortController;this.inFlightDependencies.push(e),y=n.sendAsync({type:"GI",data:{icons:m,source:this.source,tileID:this.tileID,type:"patterns"}},e);}const[v,w,x]=yield Promise.all([f,p,y]),b=new o(v),S=new e.bo(w,x);for(const t in c){const o=c[t];o instanceof e.a6?(s(o.layers,this.zoom,r),e.bp({bucket:o,glyphMap:v,glyphPositions:b.positions,imageMap:w,imagePositions:S.iconPositions,showCollisionBoxes:this.showCollisionBoxes,canonical:this.tileID.canonical})):o.hasPattern&&(o instanceof e.bq||o instanceof e.br||o instanceof e.bs)&&(s(o.layers,this.zoom,r),o.addFeatures(u,this.tileID.canonical,S.patternPositions));}return this.status="done",{buckets:Object.values(c).filter((e=>!e.isEmpty())),featureIndex:l,collisionBoxArray:this.collisionBoxArray,glyphAtlasImage:b.image,imageAtlas:S,glyphMap:this.returnDependencies?v:null,iconMap:this.returnDependencies?w:null,glyphPositions:this.returnDependencies?b.positions:null}}))}}function s(t,o,i){const s=new e.z(o);for(const e of t)e.recalculate(s,i);}class r{constructor(e,t,o){this.actor=e,this.layerIndex=t,this.availableImages=o,this.fetching={},this.loading={},this.loaded={};}loadVeLine truncated
define("index",["exports","./shared"],(function(t,e){"use strict";var i="4.7.1";let a,s;const o={now:"undefined"!=typeof performance&&performance&&performance.now?performance.now.bind(performance):Date.now.bind(Date),frameAsync:t=>new Promise(((i,a)=>{const s=requestAnimationFrame(i);t.signal.addEventListener("abort",(()=>{cancelAnimationFrame(s),a(e.c());}));})),getImageData(t,e=0){return this.getImageCanvasContext(t).getImageData(-e,-e,t.width+2*e,t.height+2*e)},getImageCanvasContext(t){const e=window.document.createElement("canvas"),i=e.getContext("2d",{willReadFrequently:!0});if(!i)throw new Error("failed to create canvas 2d context");return e.width=t.width,e.height=t.height,i.drawImage(t,0,0,t.width,t.height),i},resolveURL:t=>(a||(a=document.createElement("a")),a.href=t,a.href),hardwareConcurrency:"undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,get prefersReducedMotion(){return !!matchMedia&&(null==s&&(s=matchMedia("(prefers-reduced-motion: reduce)")),s.matches)}};class r{static testProp(t){if(!r.docStyle)return t[0];for(let e=0;e<t.length;e++)if(t[e]in r.docStyle)return t[e];return t[0]}static create(t,e,i){const a=window.document.createElement(t);return void 0!==e&&(a.className=e),i&&i.appendChild(a),a}static createNS(t,e){return window.document.createElementNS(t,e)}static disableDrag(){r.docStyle&&r.selectProp&&(r.userSelect=r.docStyle[r.selectProp],r.docStyle[r.selectProp]="none");}static enableDrag(){r.docStyle&&r.selectProp&&(r.docStyle[r.selectProp]=r.userSelect);}static setTransform(t,e){t.style[r.transformProp]=e;}static addEventListener(t,e,i,a={}){t.addEventListener(e,i,"passive"in a?a:a.capture);}static removeEventListener(t,e,i,a={}){t.removeEventListener(e,i,"passive"in a?a:a.capture);}static suppressClickInternal(t){t.preventDefault(),t.stopPropagation(),window.removeEventListener("click",r.suppressClickInternal,!0);}static suppressClick(){window.addEventListener("click",r.suppressClickInternal,!0),window.setTimeout((()=>{window.removeEventListener("click",r.suppressClickInternal,!0);}),0);}static getScale(t){const e=t.getBoundingClientRect();return {x:e.width/t.offsetWidth||1,y:e.height/t.offsetHeight||1,boundingClientRect:e}}static getPoint(t,i,a){const s=i.boundingClientRect;return new e.P((a.clientX-s.left)/i.x-t.clientLeft,(a.clientY-s.top)/i.y-t.clientTop)}static mousePos(t,e){const i=r.getScale(t);return r.getPoint(t,i,e)}static touchPos(t,e){const i=[],a=r.getScale(t);for(let s=0;s<e.length;s++)i.push(r.getPoint(t,a,e[s]));return i}static mouseButton(t){return t.button}static remove(t){t.parentNode&&t.parentNode.removeChild(t);}}r.docStyle="undefined"!=typeof window&&window.document&&window.document.documentElement.style,r.selectProp=r.testProp(["userSelect","MozUserSelect","WebkitUserSelect","msUserSelect"]),r.transformProp=r.testProp(["transform","WebkitTransform"]);const n={supported:!1,testSupport:function(t){!c&&h&&(u?d(t):l=t);}};let l,h,c=!1,u=!1;function d(t){const e=t.createTexture();t.bindTexture(t.TEXTURE_2D,e);try{if(t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,h),t.isContextLost())return;n.supported=!0;}catch(t){}t.deleteTexture(e),c=!0;}var _;"undefined"!=typeof document&&(h=document.createElement("img"),h.onload=()=>{l&&d(l),l=null,u=!0;},h.onerror=()=>{c=!0,l=null;},h.src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA="),function(t){let i,a,s,o;t.resetRequestQueue=()=>{i=[],a=0,s=0,o={};},t.addThrottleControl=t=>{const e=s++;return o[e]=t,e},t.removeThrottleControl=t=>{delete o[t],l();},t.getImage=(t,a,s=!0)=>new Promise(((o,r)=>{n.supported&&(t.headers||(t.headers={}),t.headers.accept="image/webp,*/*"),e.e(t,{type:"image"}),i.push({abortController:a,requestParameters:t,supportImageRefresh:s,state:"queued",onError:t=>{r(t);},onSuccess:t=>{o(t);}}),l();}));const r=t=>e._(this,void 0,void 0,(function*(){t.state="running";const{requestParameters:i,supportImageRefresh:s,onError:o,onSuccess:r,abortController:n}=t,c=!1===s&&!e.i(self)&&!e.g(i.url)&&(!i.headers||Object.keys(i.headers).reduce(((t,e)=>t&&"accept"===e),!0));a++;const u=c?h(i,n):e.m(i,n);try{const i=yield u;delete t.abortController,t.state="completed",i.data instanceof HTMLImageElement||e.b(i.data)?r(i):i.data&&r({data:yield(d=i.data,"function"==typeof createImageBitmap?e.d(d):e.f(d)),cacheControl:i.cacheControl,expires:i.expires});}catch(e){delete t.abortController,o(e);}finally{a--,l();}var d;})),l=()=>{const t=(()=>{for(const t of Object.keys(o))if(o[t]())return !0;return !1})()?e.a.MAX_PARALLEL_IMAGE_REQUESTS_PER_FRAME:e.a.MAX_PARALLEL_IMAGE_REQUESTS;for(let e=a;e<t&&i.length>0;e++){const t=i.shift();t.abortController.signal.aborted?e--:r(t);}},h=(t,i)=>new Promise(((a,s)=>{const o=new Image,r=t.url,n=t.credentials;n&&"include"===n?o.crossOrigin="use-credentials":(n&&"same-origin"===n||!e.s(r))&&(o.crossOrigin="anonymous"),i.signal.addEventListener("abort",(()=>{o.src="",s(e.c());})),o.fetchPriority="high",o.onload=()=>{o.onerror=o.onload=null,a(Line truncated
//
// Our custom intro provides a specialized "define()" function, called by the
// AMD modules below, that sets up the worker blob URL and then executes the
// main module, storing its exported value as 'maplibregl'
var maplibregl$1 = maplibregl;
return maplibregl$1;
}));
//# sourceMappingURL=maplibre-gl.js.map
@@ -0,0 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-53.935418,-27.161148],[-53.851724,-27.156204],[-53.800418,-27.164751],[-53.718525,-27.157544],[-53.671493,-27.169475],[-53.659573,-27.200523],[-53.615693,-27.209971],[-53.540096,-27.19782],[-53.498019,-27.172178],[-53.489691,-27.133242],[-53.456622,-27.103315],[-53.39812,-27.082398],[-53.347495,-27.080596],[-53.304747,-27.09769],[-53.26334,-27.163389],[-53.227118,-27.173738],[-53.18437,-27.149458],[-53.134196,-27.140449],[-53.103599,-27.154402],[-53.07075,-27.14179],[-53.030474,-27.137527],[-52.994241,-27.163389],[-52.952844,-27.178923],[-52.900198,-27.176682],[-52.826392,-27.189492],[-52.741566,-27.245742],[-52.636493,-27.259475],[-52.510721,-27.230449],[-52.414646,-27.236294],[-52.348047,-27.27635],[-52.304398,-27.283096],[-52.264792,-27.277251],[-52.228571,-27.292544],[-52.192349,-27.280855],[-52.139241,-27.283315],[-52.05959,-27.313484],[-51.984224,-27.376699],[-51.9129,-27.472544],[-51.873295,-27.509458],[-51.753599,-27.511919],[-51.701622,-27.488079],[-51.644691,-27.499768],[-51.582366,-27.547471],[-51.53017,-27.56885],[-51.487642,-27.564126],[-51.40642,-27.613169],[-51.286724,-27.716221],[-51.190417,-27.781238],[-51.117292,-27.807803],[-50.965868,-27.959458],[-50.736144,-28.235984],[-50.577523,-28.386277],[-50.446566,-28.422048],[-50.233047,-28.46863],[-50.154967,-28.473794],[-50.141025,-28.452876],[-50.06879,-28.444329],[-49.938525,-28.448152],[-49.839066,-28.465247],[-49.736695,-28.511367],[-49.70812,-28.530264],[-49.699122,-28.597105],[-49.79745,-28.623867],[-49.846493,-28.695432],[-49.953599,-28.793518],[-49.94482,-28.949898],[-50.007146,-29.101553],[-50.069691,-29.092764],[-50.163525,-29.195376],[-50.168019,-29.284695],[-50.051915,-29.356018],[-50.03392,-29.320466],[-50.0787,-29.244419],[-49.953599,-29.195376],[-49.868773,-29.217656],[-49.815215,-29.271204],[-49.712844,-29.324531]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-38.221101,-9.347651],[-38.154534,-9.266769],[-38.092033,-9.186415],[-37.967031,-9.141767],[-37.815244,-8.976598],[-37.806312,-8.896222],[-37.748282,-8.860517],[-37.68132,-8.976598],[-37.62774,-8.981059],[-37.520601,-8.945331],[-37.471492,-9.003383],[-37.391138,-9.043549],[-37.18131,-9.239985],[-36.998278,-9.306935],[-36.940248,-9.356066],[-36.904532,-9.289094],[-36.833099,-9.262309],[-36.578633,-9.293554],[-36.440239,-9.20874],[-36.346492,-9.199797],[-36.266138,-9.141767],[-36.239354,-9.088198],[-36.123272,-9.016764],[-36.118812,-8.967656],[-36.016133,-8.896222],[-35.971496,-8.905165],[-35.88221,-8.873898],[-35.801845,-8.86946],[-35.721491,-8.918547],[-35.609881,-8.864977],[-35.471486,-8.833732],[-35.391132,-8.882841],[-35.301846,-8.887302],[-35.150059,-8.914086]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-38.699171,-7.619435],[-38.819713,-7.664084],[-38.86435,-7.704272],[-38.967028,-7.847116],[-38.998284,-7.820331],[-39.074178,-7.856059],[-39.109895,-7.753381],[-39.243818,-7.681948],[-39.306319,-7.623918],[-39.350968,-7.548024],[-39.534,-7.476591],[-39.654531,-7.373913],[-39.846495,-7.347128],[-39.953645,-7.36051],[-40.065255,-7.405158],[-40.172393,-7.418561],[-40.261679,-7.391755],[-40.538469,-7.391755]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-34.809747,-7.566789],[-34.980419,-7.512297],[-35.065245,-7.409618],[-35.270601,-7.382834],[-35.373279,-7.458727],[-35.498271,-7.454267],[-35.556312,-7.655163],[-35.681314,-7.713193],[-35.712559,-7.708732],[-35.837485,-7.744394],[-35.886703,-7.732177],[-35.931308,-7.838195],[-35.967025,-7.815871],[-36.074174,-7.824792],[-36.123272,-7.780165],[-36.167921,-7.824792],[-36.217029,-7.780165],[-36.266138,-7.824792],[-36.33756,-7.81141],[-36.422386,-7.824792],[-36.449171,-7.909628],[-36.569702,-7.92301],[-36.658987,-8.012306],[-36.627742,-8.083718],[-36.78399,-8.222123],[-36.833099,-8.226584],[-36.962562,-8.284636],[-37.060779,-8.231044],[-37.127741,-8.164094],[-37.150065,-7.976579],[-37.212567,-7.958737],[-37.342029,-7.998903],[-37.225959,-7.815871],[-37.167918,-7.762302],[-37.212567,-7.646242],[-37.199174,-7.574809],[-37.016131,-7.507836],[-37.016131,-7.400698],[-37.150065,-7.347128],[-37.252744,-7.271235],[-37.34649,-7.298019],[-37.382206,-7.351589],[-37.435776,-7.347128],[-37.551846,-7.476591],[-37.708105,-7.548024],[-37.792919,-7.637299],[-37.895598,-7.686408],[-37.975963,-7.771222],[-38.057404,-7.757841],[-38.087562,-7.820331],[-38.150063,-7.771222],[-38.167927,-7.80695],[-38.225957,-7.81141],[-38.257213,-7.851598],[-38.30185,-7.829274],[-38.304113,-7.782385],[-38.355419,-7.699811],[-38.417921,-7.748898],[-38.449177,-7.735517],[-38.52507,-7.766762],[-38.587572,-7.739977],[-38.592032,-7.690868],[-38.645601,-7.677487],[-38.699171,-7.619435]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-40.538469,-7.391755],[-40.645607,-7.400698],[-40.690256,-7.427482]Line truncated
@@ -0,0 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[180,-16.15293],[179.848145,-16.214258],[179.788867,-16.221484],[179.715039,-16.207617],[179.635254,-16.223242],[179.551758,-16.249902],[179.475098,-16.294141],[179.35918,-16.379883],[179.293555,-16.398633],[179.224609,-16.405176],[179.091406,-16.4375],[178.960547,-16.482813],[178.865723,-16.540039],[178.805078,-16.631445],[178.744531,-16.63418],[178.686328,-16.665625],[178.634277,-16.648535],[178.583594,-16.621875],[178.567773,-16.663867],[178.541992,-16.700488],[178.51377,-16.726074],[178.497461,-16.787891],[178.603711,-16.800586],[178.638086,-16.85127],[178.665039,-16.92002],[178.706641,-16.976172],[178.80293,-16.952148],[178.883691,-16.886035],[178.950391,-16.904004],[179.006836,-16.900195],[179.055469,-16.813574],[179.202344,-16.712695],[179.300488,-16.710352],[179.32334,-16.718066],[179.345996,-16.769727],[179.375,-16.791992],[179.419336,-16.806543],[179.46543,-16.806055],[179.588965,-16.787012],[179.714746,-16.743555],[179.820801,-16.736914],[179.92793,-16.744434],[179.890039,-16.666992],[179.905957,-16.583594],[179.926563,-16.55166],[179.930371,-16.519434],[179.884961,-16.518457],[179.841016,-16.5375],[179.69707,-16.631934],[179.568164,-16.747461],[179.56416,-16.636914],[179.619141,-16.527734],[179.748145,-16.446289],[179.793848,-16.370313],[179.848242,-16.30166],[179.999219,-16.168555]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[177.25752,-17.054199],[177.287402,-17.048633],[177.275781,-17.104883],[177.23418,-17.14707],[177.182813,-17.163867],[177.210156,-17.084277],[177.239258,-17.059375],[177.25752,-17.054199]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[127.372656,0.791309],[127.353809,0.847461],[127.319824,0.862012],[127.292773,0.84248],[127.286426,0.811914],[127.306055,0.769434],[127.338379,0.758447],[127.372656,0.791309]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-81.322314,24.685059],[-81.420068,24.75],[-81.42168,24.732617],[-81.379053,24.66626],[-81.379053,24.636279],[-81.364795,24.629932],[-81.334814,24.650488],[-81.319824,24.667627],[-81.322314,24.685059]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-80.799414,24.846289],[-80.838867,24.817871],[-80.84834,24.803662],[-80.829395,24.803662],[-80.786768,24.821045],[-80.785205,24.835254],[-80.799414,24.846289]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-80.625684,24.941113],[-80.665137,24.898438],[-80.638281,24.903174],[-80.6146,24.937939],[-80.625684,24.941113]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[129.568848,-31.627246],[129.187695,-31.659961],[128.946191,-31.702637],[128.546094,-31.887695],[128.067676,-32.066504],[127.678027,-32.15127],[127.319824,-32.264063],[127.084082,-32.296875],[126.779297,-32.310938],[126.136523,-32.256836],[125.917188,-32.296973],[125.56748,-32.505859],[125.463672,-32.556543],[125.266602,-32.614453],[124.758789,-32.882715],[124.524609,-32.940137],[124.373242,-32.958398],[124.24375,-33.015234],[124.126074,-33.129395],[123.967188,-33.446289],[123.868359,-33.596387],[123.650391,-33.836328],[123.506836,-33.916211],[123.36543,-33.905371],[123.207617,-33.988281],[123.067578,-33.900586],[122.955664,-33.883789],[122.777539,-33.89082],[122.150977,-33.991797],[122.061133,-33.874414],[121.946387,-33.856738],[121.729688,-33.8625],[121.405078,-33.826758],[120.814551,-33.871289],[120.530566,-33.919727],[120.418359,-33.963086],[120.209375,-33.935449],[119.854102,-33.974707],[119.729102,-34.041504],[119.635156,-34.101172],[119.450586,-34.368262],[119.247656,-34.456445],[119.081348,-34.459375],[118.895312,-34.479883],[118.520117,-34.737109],[118.135547,-34.986621],[118.006445,-35.013281],[117.863086,-35.05498],[117.675391,-35.074902],[117.581934,-35.097754],[117.143945,-35.033691],[116.86543,-35.026563],[116.517188,-34.987891],[116.21709,-34.86582],[115.986719,-34.79502],[115.72627,-34.526074],[115.565039,-34.425781],[115.277637,-34.303906],[115.194824,-34.308496],[115.12793,-34.341797],[115.008789,-34.255859],[115.005664,-34.145117],[114.973437,-34.051172],[114.975684,-33.804199],[114.993848,-33.515332],[115.098926,-33.580273],[115.181641,-33.643457],[115.358789,-33.639941],[115.515332,-33.531348],[115.604492,-33.372266],[115.683008,-33.192871],[115.670898,-33.002148],[115.618555,-32.666992],[115.654297,-32.596582],[115.70791,-32.567969],[115.725391,-32.401074],[115.738086,-31.887891],[115.698438,-31.694531],[115.45459,-31.302539],[115.294336,-30.961816],[115.176855,-30.808008],[115.07793,-30.560449],[114.994531,-30.216211],[114.968848,-30.042285],[114.94209,-29.721582],[114.971387,-29.539746],[114.958984,-29.433594],[114.856836,-29.142969],[114.628418,-28.871777],[114.590625,-28.77168],[114.591797,-28.666211],[114.537402,-28.542871],[114.353516,-28.294922],[114.165Line truncated
@@ -0,0 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[17.979785,59.329053],[17.876172,59.270801],[17.570508,59.267627],[17.474512,59.291504],[17.370703,59.294922],[17.30459,59.272168],[17.175195,59.355811],[17.065625,59.373242],[16.913867,59.44585],[16.742285,59.430615],[16.610449,59.453516],[16.144336,59.447754],[16.044238,59.478467],[16.251758,59.493213],[16.472656,59.519385],[16.573828,59.61167],[16.646875,59.559277],[16.752344,59.543311],[16.841016,59.5875],[16.977539,59.550684],[17.062695,59.569238],[17.37207,59.495752],[17.390527,59.584473],[17.534473,59.539404],[17.687305,59.541602],[17.671582,59.594775],[17.760059,59.620508],[17.785938,59.597998],[17.808594,59.553223],[17.772852,59.414111],[17.829297,59.379004],[17.964258,59.359375],[17.979785,59.329053]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[29.836719,61.226074],[29.836719,61.226074],[29.836719,61.226074],[29.836719,61.226074],[29.860156,61.239307],[29.864551,61.250977],[29.855078,61.27124],[29.891309,61.294971],[29.949414,61.289404],[30.029102,61.254492],[30.079297,61.242627],[30.094141,61.258545],[30.089453,61.270557],[30.090039,61.299268],[30.12998,61.312598],[30.173926,61.314062],[30.192969,61.327246],[30.195996,61.347803],[30.182617,61.361084],[30.179297,61.372656],[30.200879,61.388965],[30.209961,61.417969],[30.216895,61.431348],[30.237305,61.446338],[30.252051,61.458643],[30.251563,61.472656],[30.234863,61.492627],[30.23877,61.514844],[30.285156,61.516602],[30.466211,61.487158],[30.480664,61.490039],[30.486328,61.514941],[30.512695,61.525195],[30.550781,61.535449],[30.605859,61.541309],[30.616699,61.560303],[30.593359,61.579492],[30.594336,61.605811],[30.75752,61.733447],[30.851953,61.775049],[30.888184,61.764795],[30.895898,61.754443],[30.905664,61.742236],[31.025879,61.714355],[31.056738,61.689209],[31.071094,61.645117],[31.098438,61.620361],[31.160645,61.618701],[31.172266,61.629785],[31.238184,61.643018],[31.25752,61.641064],[31.285938,61.615234],[31.308105,61.605029],[31.327832,61.61665],[31.334082,61.627441],[31.364844,61.627441],[31.486133,61.55083],[31.533203,61.497363],[31.575781,61.437207],[31.603613,61.422559],[31.619727,61.426953],[31.616504,61.44248],[31.62041,61.461084],[31.636914,61.462744],[31.697852,61.415479],[31.762305,61.383691],[31.870703,61.347363],[31.898633,61.323975],[32.35957,61.180518],[32.526855,61.117529],[32.563379,61.079443],[32.58125,61.044873],[32.580566,61.013867],[32.592578,60.997607],[32.633398,60.986719],[32.640723,60.969141],[32.76543,60.886035],[32.807813,60.826367],[32.811914,60.756494],[32.843555,60.712207],[32.903027,60.693506],[32.936523,60.670312],[32.944336,60.642578],[32.906934,60.585938],[32.841406,60.497021],[32.815723,60.481885],[32.781836,60.488232],[32.72207,60.521143],[32.641699,60.536865],[32.599902,60.533496],[32.596484,60.511084],[32.617871,60.475586],[32.663965,60.4271],[32.67666,60.392432],[32.655859,60.37168],[32.594922,60.35459],[32.590234,60.330225],[32.614355,60.285791],[32.612305,60.245361],[32.583887,60.208936],[32.500391,60.176514],[32.361523,60.148096],[32.260156,60.154346],[32.196094,60.195361],[32.057129,60.226514],[31.843262,60.247852],[31.699414,60.235645],[31.598926,60.169629],[31.590625,60.155566],[31.596875,60.143799],[31.605859,60.142285],[31.606445,60.133643],[31.548242,60.071875],[31.540918,60.034521],[31.565039,59.998047],[31.567188,59.966553],[31.547168,59.940137],[31.509766,59.920361],[31.454785,59.907178],[31.401367,59.911328],[31.349316,59.932764],[31.274414,59.937646],[31.176367,59.925781],[31.10625,59.927686],[31.063965,59.943213],[31.042773,59.950977],[31.063184,59.962891],[31.145313,60.005518],[31.142871,60.031689],[31.105957,60.056299],[31.095117,60.08374],[31.114941,60.134863],[31.108984,60.146436],[31.018945,60.19292],[30.956836,60.263623],[30.849121,60.442627],[30.832129,60.45752],[30.816895,60.458838],[30.80332,60.446631],[30.775977,60.448096],[30.774219,60.49209],[30.758594,60.524512],[30.72373,60.561768],[30.663281,60.592822],[30.533887,60.630078],[30.523926,60.65459],[30.504004,60.703564],[30.504492,60.731641],[30.525391,60.738867],[30.533203,60.765527],[30.52793,60.811719],[30.502051,60.843213],[30.455859,60.86001],[30.441211,60.873633],[30.43584,60.902686],[30.408691,60.928027],[30.292676,61.005127],[30.124902,61.087354],[29.857227,61.187988],[29.825,61.215576],[29.836719,61.226074]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[31.471582,2.385449],[31.395508,2.28042],[31.383984,2.260596],[31.387402,2.200244],[31.399805,1.895508],[31.387402,1.856934],[31.368555,1.836475],[31.343457,1.834033],[31.32207,1.802588],[31.30459,1.742188],[31.267871,1.687549],[31.16582,1.605469],[31.129297,1.587793],[31.039844,1.563623],[31.01416,1.546436],[30.969141,1.527148],[30.892676,1.448096],[30.784863,1.309229],[30.726855,1.223535],[30.718555,1.191113],[30.59375,1.04126],[30.56875,1.028613],[30.539258,1.021484],[30.50732Line truncated
@@ -0,0 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"expressway":0,"type":"secondary highway","level":"federal","min_zoom":7.0},"geometry":{"type":"LineString","coordinates":[[-100.505428,42.807529],[-100.534951,42.788427],[-100.54016,42.637343],[-100.536687,42.527938],[-100.54537,42.361225],[-100.528004,42.329966],[-100.533214,42.251819],[-100.526268,42.178882],[-100.54016,42.100735],[-100.541897,41.982647],[-100.557526,41.98091],[-100.554053,41.939232],[-100.562736,41.89929],[-100.557526,41.857612],[-100.547107,41.83851],[-100.543634,41.810724],[-100.526268,41.774256],[-100.522794,41.75168],[-100.508902,41.732577],[-100.514111,41.685689],[-100.501955,41.654431],[-100.489799,41.600596],[-100.500219,41.545025],[-100.503692,41.446039],[-100.500219,41.421727],[-100.559263,41.371366],[-100.611361,41.319268],[-100.628727,41.310585],[-100.651302,41.275853],[-100.677351,41.246331],[-100.70861,41.220282],[-100.748552,41.197706],[-100.771127,41.102194]]}},{"type":"Feature","properties":{"expressway":0,"type":"secondary highway","level":"federal","min_zoom":7.1},"geometry":{"type":"LineString","coordinates":[[-87.727566,44.151653],[-87.573009,44.14818]]}},{"type":"Feature","properties":{"expressway":1,"type":"major highway","level":"state","min_zoom":6.0},"geometry":{"type":"MultiLineString","coordinates":[[[-74.759203,39.143013],[-74.673585,39.233618],[-74.6458,39.310028]],[[-74.644063,39.310028],[-74.616278,39.346496],[-74.55376,39.41075]]]}},{"type":"Feature","properties":{"expressway":0,"type":"secondary highway","level":"federal","min_zoom":7.1},"geometry":{"type":"LineString","coordinates":[[-104.450971,42.757168],[-104.438815,42.790163],[-104.402346,42.849208],[-104.398873,42.882203],[-104.40582,42.887413],[-104.404083,42.908252],[-104.360668,42.946457],[-104.367615,43.03155],[-104.365878,43.090594],[-104.294678,43.255571],[-104.303361,43.292039],[-104.301624,43.304195],[-104.228687,43.370186],[-104.204375,43.410128],[-104.199165,43.427494],[-104.211321,43.451806],[-104.211321,43.462225],[-104.204375,43.498694],[-104.193955,43.616782],[-104.188745,43.774812],[-104.183536,43.814754],[-104.197428,43.851223],[-104.187009,43.892901],[-104.183536,43.929369],[-104.199165,43.971048],[-104.162696,44.017936],[-104.15054,44.042248],[-104.136648,44.08219],[-104.131438,44.132551],[-104.100179,44.137761],[-104.072394,44.156863],[-104.06892,44.170756],[-104.055028,44.175966]]}},{"type":"Feature","properties":{"expressway":0,"type":"secondary highway","level":"federal","min_zoom":7.1},"geometry":{"type":"LineString","coordinates":[[-104.053291,44.175966],[-104.037662,44.177702],[-104.037662,44.195068],[-103.985564,44.205488],[-103.959515,44.226327],[-103.92131,44.2298],[-103.92131,44.24022],[-103.928256,44.243693],[-103.92652,44.257586],[-103.90568,44.281898],[-103.867475,44.299264],[-103.836217,44.292318],[-103.820587,44.295791],[-103.798012,44.314893],[-103.799748,44.330523],[-103.721601,44.379147],[-103.714655,44.391303],[-103.711182,44.426035],[-103.733758,44.472923]]}},{"type":"Feature","properties":{"expressway":0,"type":"secondary highway","level":"federal","min_zoom":3.0},"geometry":{"type":"LineString","coordinates":[[-92.979028,16.727353],[-92.959925,16.749929],[-92.933876,16.755139],[-92.925194,16.769032],[-92.913037,16.769032],[-92.909564,16.774241],[-92.907828,16.829812],[-92.899145,16.855861],[-92.925194,16.89233],[-92.911301,16.921852],[-92.918247,16.934008],[-92.906091,16.982633],[-92.888725,16.989579],[-92.862676,16.986106],[-92.820998,16.996525],[-92.848783,17.026048],[-92.853993,17.046887],[-92.876569,17.095511],[-92.866149,17.112877],[-92.867886,17.11635],[-92.904354,17.180604],[-92.914774,17.191024],[-92.951242,17.20839],[-92.987711,17.20318],[-92.999867,17.189287],[-93.010287,17.192761],[-93.025916,17.267434],[-93.00334,17.321269],[-93.00334,17.328215],[-93.032862,17.409835],[-93.036336,17.435884],[-93.045019,17.446303],[-93.062384,17.45325],[-93.098853,17.439357],[-93.112746,17.501874],[-93.08496,17.515767],[-93.069331,17.550499],[-93.017233,17.548762]]}},{"type":"Feature","properties":{"expressway":1,"type":"major highway","level":"federal","min_zoom":7.0},"geometry":{"type":"LineString","coordinates":[[-98.126294,20.127605],[-98.157553,20.061615],[-98.161026,20.044249],[-98.121084,20.009517],[-98.105455,20.00778],[-98.079406,19.995624],[-98.07246,19.986941],[-98.068986,19.959156],[-98.060303,19.950473],[-98.020362,19.945263],[-98.006469,19.93137],[-97.996049,19.933107],[-97.982157,19.945263],[-97.97521,19.940053],[-97.973474,19.93137],[-97.97521,19.907058],[-97.982157,19.894902],[-98.018625,19.865379],[-98.037728,19.839331],[-98.025572,19.799389],[-98.055094,19.749028],[-98.082879,19.719506],[-98.084616,19.705613],[-98.075933,19.693457],[-98.088089,19.683037]]}},{"type":"Feature","properties":{"expressway":1,"type":"major highway","level":"interstate","min_zoom":3.0},"geometry":{"type":"LineString","coordinates":[[-99.308915,19.340928],[-99.267237,19.363504],[-99.234241,19.39997Line truncated
@@ -0,0 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"name":"Arctic Ocean"},"geometry":{"type":"Polygon","coordinates":[[[179.527885,71.440187],[156.042393,77.470052],[138.844476,76.252969],[96.196795,81.26734],[96.18677,81.183753],[96.075097,81.192719],[95.984095,81.211271],[95.902085,81.260467],[95.800489,81.280569],[95.159649,81.27106],[95.159649,81.280569],[91.554443,81.191685],[91.389854,81.187448],[91.108889,81.199101],[90.070141,81.213906],[89.901314,81.170524],[89.919297,81.14882],[89.870101,81.14776],[65.360693,81.068385],[65.309895,81.09642],[65.171764,81.144065],[65.135797,81.150396],[64.919893,81.280052],[63.808643,81.597552],[63.676351,81.617137],[63.782185,81.649952],[63.767354,81.664215],[63.709683,81.687521],[63.04931,81.710258],[62.379893,81.809219],[59.363643,81.862135],[59.358372,81.835677],[59.262047,81.854177],[58.134364,81.827719],[58.000522,81.800227],[57.98781,81.862135],[55.818226,81.862135],[50.383158,81.427692],[49.826447,80.895347],[49.507914,80.865194],[49.244364,80.821269],[49.147574,80.712257],[49.192533,80.656162],[49.185143,80.558804],[49.087733,80.515913],[48.894101,80.51276],[48.62528,80.508523],[48.464928,80.558261],[48.402503,80.568829],[48.407774,80.643993],[48.62528,80.629187],[48.683468,80.633399],[48.686672,80.717554],[48.547456,80.77892],[48.44586,80.805921],[48.345349,80.81915],[48.243185,80.823387],[47.899795,80.812794],[47.442045,80.853541],[47.352593,80.853024],[47.020831,80.814396],[46.799087,80.755123],[46.327643,80.735021],[45.124305,80.652442],[44.97403,80.624407],[44.97031,80.645052],[27.035797,80.115885],[27.017297,80.12542],[26.860614,80.15981],[26.436764,80.175675],[25.83618,80.175158],[25.750966,80.187845],[25.666837,80.209549],[25.471552,80.232829],[24.906935,80.276754],[24.785754,80.300577],[24.736041,80.301094],[24.613774,80.285746],[24.546595,80.295254],[24.402676,80.35507],[24.29736,80.360367],[24.28041,80.329128],[24.233849,80.303212],[24.142847,80.295254],[23.952885,80.304789],[23.772947,80.244482],[23.687733,80.206371],[23.353335,80.178853],[23.114693,80.186785],[23.224712,80.317475],[23.250137,80.380985],[23.315197,80.425427],[23.25117,80.446614],[23.007774,80.474132],[22.89667,80.468835],[22.792387,80.432869],[22.672291,80.412741],[22.548991,80.416461],[22.450547,80.402173],[22.417733,80.365663],[22.44631,80.308483],[22.442641,80.190506],[22.376495,80.089427],[22.289679,80.049223],[22.190201,80.059791],[21.897609,80.132267],[21.780614,80.138649],[21.696485,80.159268],[21.654679,80.218541],[21.549414,80.24288],[20.998543,80.238643],[20.897981,80.249753],[20.693187,80.298458],[20.475732,80.371477],[20.359305,80.401088],[20.104231,80.430207],[19.851274,80.470954],[19.733297,80.477827],[19.614235,80.462479],[19.691491,80.402173],[19.777222,80.353494],[19.810553,80.327035],[19.802078,80.294737],[19.751281,80.22699],[19.568191,80.249753],[19.32743,80.323315],[19.26392,80.336027],[19.178189,80.331789],[19.157053,80.30161],[19.191418,80.262982],[19.354922,80.185183],[19.536978,80.162963],[19.343295,80.116402],[19.142739,80.138649],[18.961768,80.174616],[18.779195,80.193684],[18.089677,80.170921],[17.95847,80.149733],[-25.890404,83.278716],[-25.947558,83.289826],[-27.034468,83.377133],[-27.688511,83.410465],[-28.376945,83.437466],[-28.483863,83.43483],[-28.991842,83.504671],[-29.952817,83.565003],[-30.703159,83.59358],[-32.984415,83.599394],[-33.398214,83.577173],[-33.837412,83.53007],[-34.132175,83.528494],[-34.428513,83.557587],[-34.667698,83.571333],[-34.941789,83.568698],[-35.165626,83.545934],[-35.451913,83.538545],[-36.606546,83.536943],[-36.644657,83.52901],[-36.672175,83.509942],[-36.689641,83.479789],[-36.804466,83.466017],[-37.123026,83.468135],[-37.487086,83.499374],[-37.723092,83.497798],[-37.827866,83.485628],[-37.960701,83.437466],[-38.071288,83.412067],[-38.187715,83.402532],[-38.541182,83.414702],[-38.648075,83.400956],[-38.749671,83.370777],[-38.761324,83.485085],[-40.358332,83.467102],[-69.858857,83.132135],[-69.863611,83.108312],[-69.867849,83.109372],[-69.969987,83.116271],[-70.87063,83.098261],[-71.084932,83.082914],[-71.423594,83.021005],[-71.402432,83.00142],[-71.198181,82.96969],[-70.933055,82.911477],[-70.940471,82.902485],[-71.132035,82.923104],[-71.406153,82.974961],[-71.983456,83.10144],[-72.069187,83.106194],[-72.811597,83.081337],[-73.331255,82.998784],[-73.403757,82.97708],[-73.440783,82.945868],[-73.441842,82.904604],[-73.234413,82.844246],[-72.776147,82.755879],[-72.658686,82.721514],[-73.271982,82.77177],[-73.703248,82.851687],[-73.916516,82.904061],[-74.055681,82.955402],[-74.197507,82.988733],[-74.413928,83.013073],[-75.744777,83.046921],[-77.124848,83.008319],[-77.041235,82.967546],[-76.908427,82.919409],[-76.40993,82.815695],[-76.187695,82.758024],[-76.087159,82.723607],[-75.642638,82.643716],[-75.565382,82.608782],[-75.74426,82.572273],[-76.00936,82.535247],[-76.146406,82.550052],[-76.24379,82.604028],[-76.335335,82.644232],[-76.42104,82.670691],[-77.225927,82.837399],[-77.479374,82.8829],[-77.Line truncated
@@ -0,0 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"featurecla":"plain","name":"NORTH CHINA PLAIN"},"geometry":{"type":"MultiPolygon","coordinates":[[[[117.625011,39.406073],[117.541148,39.052985],[117.527354,38.904975],[117.559754,38.706715],[117.553809,38.691455],[117.557813,38.625146],[117.58075,38.578234],[117.601573,38.450812],[117.622447,38.413092],[117.657237,38.384955],[117.720454,38.358711],[117.766699,38.31167],[117.909434,38.237916],[117.945201,38.200934],[118.03297,38.173102],[118.126142,38.164776],[118.535354,38.096246],[118.534068,38.059943],[118.565196,38.063544],[118.75597,38.126563],[118.8,38.12666],[118.857373,38.092293],[118.946666,38.020087],[118.972502,37.991289],[119.027539,37.904004],[119.035645,37.80918],[119.038477,37.776514],[119.070312,37.748584],[119.073521,37.740437],[119.011996,37.651121],[118.99082,37.641357],[118.982893,37.608874],[118.941661,37.549018],[118.922984,37.495917],[118.918468,37.438056],[118.928355,37.368476],[118.99232,37.166388],[119.144176,34.921332],[119.16989,34.863538],[119.165332,34.848828],[119.200977,34.748438],[119.320396,34.749213],[119.473235,34.674891],[119.58291,34.582227],[119.769727,34.496191],[119.915534,34.459813],[120.122569,34.359137],[120.227306,34.287421],[120.271495,34.240302],[120.310557,34.183295],[120.372099,34.023735],[120.425684,33.866309],[120.45808,33.800812],[120.50162,33.687924],[120.504785,33.638184],[120.60672,33.50239],[120.620797,33.479477],[120.734473,33.236621],[120.868167,33.021219],[120.872034,33.010299],[120.873561,33.00023],[120.880995,32.814009],[120.853027,32.764111],[120.853223,32.661377],[120.989941,32.567041],[121.293359,32.457324],[121.341699,32.425049],[121.375196,32.395028],[121.385265,32.379401],[121.397349,32.347968],[121.401836,32.323328],[121.403906,32.20625],[121.450781,32.15332],[121.470772,32.137112],[121.479747,32.127265],[121.500255,32.114447],[121.521739,32.108649],[121.523616,32.108472],[121.674219,32.051025],[121.751074,31.992871],[121.774492,31.966065],[121.85888,31.703271],[121.763574,31.699512],[121.680859,31.712158],[121.351953,31.858789],[121.266406,31.862695],[121.145801,31.842334],[120.973535,31.869385],[120.791699,32.031738],[120.660547,32.081055],[120.520117,32.105859],[120.184082,31.966162],[120.09873,31.975977],[120.073926,31.960254],[120.035937,31.936279],[120.191602,31.906348],[120.347461,31.9521],[120.497168,32.019824],[120.715527,31.98374],[120.752246,31.922852],[120.787793,31.819775],[120.9375,31.750195],[121.055371,31.719434],[121.204883,31.628076],[121.350977,31.485352],[121.660645,31.319727],[121.762332,31.192439],[121.810844,30.888159],[121.769434,30.870361],[121.675195,30.86377],[121.527539,30.840967],[121.454482,30.806541],[121.43922,30.805976],[121.396251,30.791266],[121.373546,30.772956],[121.363902,30.750861],[121.362706,30.743307],[121.309961,30.699707],[120.997656,30.558252],[120.968893,30.515367],[120.925059,30.468329],[120.868398,30.378095],[120.826747,30.35727],[120.768443,30.370246],[120.745588,30.368997],[120.62998,30.390869],[120.449805,30.387842],[120.245508,30.283545],[120.233238,30.273359],[120.206432,30.273505],[120.144542,30.299933],[120.094249,30.362799],[120.076671,30.434943],[120.090587,30.672858],[120.074107,30.748053],[120.025645,30.815558],[119.931895,30.871894],[119.380015,31.044379],[117.979381,30.953742],[117.743663,30.848273],[117.712657,30.840766],[117.680186,30.841254],[117.644542,30.851508],[117.603526,30.873297],[116.85731,31.642768],[116.616222,31.818427],[116.536388,31.856147],[116.086192,31.924994],[116.013194,31.958075],[115.885631,32.069953],[115.781871,32.115668],[115.033336,32.294379],[114.638438,32.516303],[114.476573,32.658087],[113.542369,33.933966],[113.096446,34.964606],[113.09315,35.055854],[113.178966,35.130561],[114.070445,35.429755],[114.16566,35.509467],[114.518565,36.39112],[114.527354,36.494757],[114.520518,36.549018],[114.493419,36.63172],[114.41325,36.75706],[114.404918,36.770087],[114.395518,36.799689],[114.389659,36.837043],[114.388927,36.874457],[114.41074,37.027248],[114.445567,37.271186],[114.402559,37.907961],[114.359019,38.552619],[114.396007,38.853827],[114.435802,38.970649],[114.453624,38.998053],[114.592052,39.138251],[114.608775,39.172309],[114.633189,39.262702],[114.650157,39.514167],[114.667247,39.58277],[114.7046,39.63935],[114.771739,39.681464],[114.877452,39.706366],[115.052379,39.705268],[115.673839,39.50019],[115.740855,39.502082],[115.797618,39.520331],[115.846934,39.555487],[115.891368,39.607978],[116.14564,40.060004],[116.312389,40.236029],[116.408946,40.298835],[116.51295,40.336127],[116.623668,40.341376],[116.701793,40.319831],[116.77589,40.276984],[117.182384,39.873053],[117.487804,39.699774],[117.548839,39.650214],[117.595103,39.58808],[117.621959,39.508368],[117.625011,39.406073]]],[[[121.854994,31.489614],[121.780469,31.46377],[121.519922,31.549609],[121.336426,31.64375],[121.226855,31.758105],[121.211133,31.805371],[121.338965,31.797363],[121.46416,31.756445],[121.491797,31.693652],Line truncated
@@ -0,0 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"featurecla":"desert","name":"Makgadikgadi Pan"},"geometry":{"type":"Polygon","coordinates":[[[24.998546,-20.071832],[25.157237,-20.230645],[25.382091,-20.12481],[25.615855,-20.001275],[25.792247,-20.010064],[25.915782,-20.204156],[26.268565,-20.226251],[26.317027,-20.296808],[26.294932,-20.645074],[26.312633,-20.918512],[26.219981,-21.152276],[25.990733,-21.196344],[25.730601,-21.086114],[25.461558,-21.134576],[25.104381,-21.116998],[24.848643,-21.130182],[24.39442,-21.028741],[24.310557,-20.698053],[24.420909,-20.561334],[24.5619,-20.270318],[24.667735,-20.018976],[24.875011,-19.970392],[24.998546,-20.071832]]]}},{"type":"Feature","properties":{"featurecla":"desert","name":"Etosha Pan"},"geometry":{"type":"Polygon","coordinates":[[[16.800914,-18.925226],[16.236339,-19.119317],[15.865977,-19.101739],[15.830699,-18.713556],[16.289317,-18.466729],[16.536265,-18.325494],[16.871349,-18.466729],[17.083018,-18.695977],[16.800914,-18.925226]]]}},{"type":"Feature","properties":{"featurecla":"desert","name":"Jabal as Sawd\u00e4\u2019"},"geometry":{"type":"Polygon","coordinates":[[[16.483287,28.505683],[16.112926,28.470404],[15.204478,28.496833],[14.816417,28.831977],[14.772349,29.281806],[15.072154,29.678656],[15.39857,29.952033],[15.874766,29.758002],[16.253917,29.616889],[16.994762,29.422858],[16.880138,28.893744],[16.483287,28.505683]]]}},{"type":"Feature","properties":{"featurecla":"desert","name":"White Sands"},"geometry":{"type":"Polygon","coordinates":[[[-106.200978,32.771064],[-106.343251,32.752143],[-106.457021,32.83747],[-106.466542,33.017646],[-106.33373,33.084052],[-106.181996,33.036628],[-106.125111,32.903815],[-106.200978,32.771064]]]}},{"type":"Feature","properties":{"featurecla":"desert","name":"Black Rock Desert"},"geometry":{"type":"Polygon","coordinates":[[[-118.737172,41.021735],[-118.830068,40.893439],[-118.953969,40.804938],[-119.11321,40.751899],[-119.254811,40.725287],[-119.285756,40.796088],[-119.254811,40.840338],[-119.201711,40.884589],[-119.17516,40.933295],[-119.135365,41.00849],[-119.06017,41.08808],[-119.020314,41.05268],[-118.998219,40.950995],[-118.914113,40.97309],[-118.874318,41.083686],[-118.856618,41.273932],[-118.763722,41.362433],[-118.706227,41.437628],[-118.59557,41.424323],[-118.644276,41.273932],[-118.657521,41.154486],[-118.737172,41.021735]]]}},{"type":"Feature","properties":{"featurecla":"plateau","name":"Al Har\u00fcj al Aswad"},"geometry":{"type":"Polygon","coordinates":[[[18.101695,26.221442],[17.559337,26.181769],[17.202159,26.499274],[16.990489,26.658026],[16.223155,26.922614],[15.971812,27.478217],[16.143809,27.742804],[16.567149,28.086737],[17.17567,28.470404],[18.075206,28.298407],[18.44929,27.924379],[18.485362,27.888312],[18.495862,27.850255],[18.591197,27.504706],[18.472179,27.00196],[18.181041,26.45954],[18.101695,26.221442]]]}},{"type":"Feature","properties":{"featurecla":"plateau","name":"Horn Plat."},"geometry":{"type":"Polygon","coordinates":[[[-117.748586,62.284125],[-117.942616,62.090155],[-118.436513,62.107733],[-118.789296,62.037238],[-119.194936,61.98432],[-119.618276,61.96662],[-120.253285,61.98432],[-120.641347,62.090155],[-120.667836,62.222419],[-120.500234,62.38996],[-120.18279,62.495795],[-119.75945,62.566413],[-119.089162,62.531073],[-118.136647,62.425238],[-117.748586,62.284125]]]}},{"type":"Feature","properties":{"featurecla":"plateau","name":"Khomas Highland"},"geometry":{"type":"Polygon","coordinates":[[[16.095225,-21.491876],[16.492076,-21.879937],[16.990489,-21.787164],[17.427013,-21.601983],[17.78419,-21.681329],[18.339854,-22.435479],[18.154674,-23.044],[17.837169,-23.361505],[17.550426,-23.255792],[17.206432,-23.114557],[16.898237,-23.135594],[16.81837,-23.141046],[16.633189,-23.48504],[16.765514,-23.837823],[16.686168,-24.09356],[16.368663,-24.587457],[16.033458,-24.613946],[15.638658,-24.538265],[15.618907,-24.534478],[15.609481,-24.217654],[15.601329,-23.943658],[15.480409,-23.630624],[15.451427,-23.555597],[15.450938,-23.534178],[15.433726,-22.779474],[15.416684,-22.768112],[15.327891,-22.708917],[14.913463,-22.523736],[15.036876,-22.003351],[15.038088,-22.000408],[15.160411,-21.703546],[15.671886,-21.385919],[16.057767,-21.4825],[16.095225,-21.491876]]]}},{"type":"Feature","properties":{"featurecla":"plateau","name":"Ennedi Plat."},"geometry":{"type":"Polygon","coordinates":[[[23.671153,18.111945],[24.26344,17.74342],[24.26649,17.741523],[24.261107,17.727525],[24.001964,17.053656],[23.750621,16.193732],[23.02296,15.836554],[22.507091,16.087897],[21.660411,16.617072],[21.025401,17.27851],[20.985728,18.045844],[22.123424,17.887091],[22.692271,17.860602],[23.671153,18.111945]]]}},{"type":"Feature","properties":{"featurecla":"plateau","name":"Jos Plateau"},"geometry":{"type":"Polygon","coordinates":[[[9.335216,10.38172],[9.511607,10.364081],[9.617442,10.478766],[9.740855,10.311164],[9.709972,10.156806],[9.617442,9.914252],[9.55128,9.689399],[9.657115,9.56153],[9Line truncated
@@ -0,0 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"MultiLineString","coordinates":[[[51.937134,55.701066],[51.880866,55.686259],[51.820312,55.697455],[51.747602,55.693663],[51.662842,55.608173],[51.578711,55.599433],[51.513428,55.583124],[51.508545,55.529486],[51.48584,55.496405],[51.369141,55.467963],[51.213063,55.50265],[51.134521,55.482733],[51.079346,55.467597],[50.980225,55.466376],[50.834452,55.45631],[50.688379,55.420111],[50.411865,55.40119],[50.078027,55.381122],[49.822168,55.334662],[49.532227,55.260614],[49.382324,55.17182],[49.248085,55.113019]]]}},{"type":"Feature","properties":{},"geometry":{"type":"MultiLineString","coordinates":[[[53.693848,58.206317],[53.677148,58.273456],[53.625879,58.35705],[53.552344,58.394477],[53.464746,58.418769],[53.428955,58.434882],[53.438867,58.484565],[53.471436,58.511005],[53.491797,58.586737],[53.381104,58.671942],[53.299561,58.779975],[53.247168,58.825214],[53.201172,58.930365],[53.238672,59.011298],[53.218652,59.099482],[53.149902,59.127997],[53.063086,59.178241],[53.003174,59.209247],[52.964258,59.281855],[52.912012,59.339667],[52.866211,59.40246],[52.814551,59.493769],[52.688965,59.581562],[52.614746,59.607733],[52.608643,59.679755],[52.639063,59.71015],[52.687402,59.733759],[52.76709,59.723651],[52.803223,59.758173],[52.86582,59.798358],[52.919922,59.896308],[52.975,59.956171],[53.035059,59.973163],[53.126074,59.989618],[53.220703,60.014911],[53.322266,60.052021],[53.400879,60.055854],[53.450879,60.07392],[53.492285,60.124457],[53.526465,60.138935],[53.594922,60.137567],[53.732031,60.18371],[53.825276,60.209904],[53.896582,60.258857],[53.946777,60.292353],[53.986328,60.322699],[54.038281,60.337226],[54.105078,60.335761],[54.154688,60.357196],[54.266504,60.361274],[54.313379,60.336982],[54.391211,60.276972],[54.520508,60.238959],[54.849219,60.245185],[55.035059,60.225898],[55.146212,60.20318],[55.25,60.220966],[55.358301,60.251288],[55.571191,60.31081],[55.664258,60.416523],[55.71543,60.485419],[55.872656,60.500116],[55.979395,60.458686],[56.109619,60.423847],[56.137207,60.392279],[56.135254,60.384711],[56.164551,60.33393],[56.156152,60.287299],[56.150098,60.23371],[56.152246,60.204657],[56.168213,60.154853],[56.235742,60.158515],[56.276855,60.140497],[56.28125,60.107367],[56.29873,60.052436],[56.294336,60.000507],[56.311035,59.945258],[56.341406,59.929511],[56.36377,59.923041],[56.384668,59.916571],[56.433838,59.882685],[56.470898,59.850458],[56.549561,59.811469],[56.590918,59.76557],[56.623242,59.69123],[56.66084,59.62453],[56.683496,59.562079],[56.685791,59.451923],[56.67832,59.388788],[56.612305,59.349066],[56.575293,59.332782],[56.427002,59.304267],[56.341553,59.279364],[56.240137,59.249579],[56.209082,59.197455],[56.179297,59.098163],[56.223527,58.996622],[56.251855,58.884833],[56.267822,58.809516],[56.144531,58.69599],[56.08877,58.654853],[56.094824,58.571845],[56.176855,58.554462],[56.251221,58.522162],[56.392822,58.442694],[56.340723,58.26142],[56.334473,58.146015],[56.333252,58.136054],[56.331348,58.109735],[56.328223,58.096381],[56.322021,58.077753],[56.308496,58.058539],[56.200781,58.027899],[56.065888,58.030233],[55.890869,57.949286],[55.832031,57.945575],[55.794461,58.022366],[55.725586,58.075507],[55.660645,58.069037],[55.63252,58.015082],[55.474854,57.895819],[55.299219,57.876654],[55.246582,57.87414],[55.212305,57.832465],[55.266992,57.795966],[55.34668,57.763251],[55.399805,57.680487],[55.449316,57.589911],[55.525098,57.57746],[55.543701,57.531562],[55.455566,57.330439],[55.35498,57.313056],[55.248242,57.305609],[55.136475,57.34162],[55.080664,57.340839],[55.042578,57.305439],[55.015723,57.236591],[54.978516,57.114447],[54.922266,57.090839],[54.849854,57.085565],[54.781494,57.055365],[54.670313,57.041938],[54.539438,57.055183],[54.45332,57.041938],[54.379004,56.997748],[54.313477,56.984076],[54.22168,56.97912],[54.166992,56.941913],[54.149658,56.869892],[54.135937,56.820258],[54.088379,56.771186],[54.045654,56.753046],[53.99585,56.736151],[53.949463,56.739814],[53.916992,56.777411],[53.910156,56.812201],[53.884521,56.827899],[53.830957,56.839057],[53.797461,56.833686],[53.74585,56.805365],[53.742578,56.793647],[53.750488,56.777118],[53.751953,56.758734],[53.746973,56.714716],[53.756592,56.6737],[53.773926,56.660907],[53.780518,56.653876],[53.79248,56.612665],[53.800098,56.528144],[53.813965,56.490058],[53.841797,56.458075],[53.853516,56.419379],[53.86582,56.400653],[53.899902,56.393183],[53.974512,56.360297],[54.03457,56.343085],[54.102051,56.277777],[54.156641,56.240107],[54.18457,56.188349],[54.117188,56.158271],[54.098633,56.143671],[54.08291,56.123114],[54.046777,56.058051],[54.005859,56.028925],[53.948828,56.022284],[53.887939,56.031806],[53.834473,56.051215],[53.773682,56.051215],[53.739355,56.033564],[53.663477,55.992621],[53.643311,55.982294],[53.564941,55.96955],[53.543457,55.958686],[53.539551,55.951044],[53.538574,55.939716],[53.539648,55.917743],[53.538574,55.914252],[53.535645,55.910Line truncated
@@ -36,8 +36,10 @@ pub(super) fn static_routes() -> Result<Router> {
"unknown_ips.html",
"configuration.html",
"circuit.html",
"site_map.html",
"flow_map.html",
"all_tree_sankey.html",
"asn_analysis.html",
"asn_explorer.html",
"api_docs.html",
"chatbot.html",
@@ -209,6 +209,7 @@ pub fn get_top_n_root_queues(n_queues: usize) -> BusResponse {
0,
NetworkJsonTransport {
name: "Others".into(),
id: None,
is_virtual: false,
max_throughput: (0.0, 0.0),
current_throughput: other_bw,
@@ -224,6 +225,8 @@ pub fn get_top_n_root_queues(n_queues: usize) -> BusResponse {
parents: Vec::new(),
immediate_parent: None,
node_type: None,
latitude: None,
longitude: None,
},
));
}
@@ -46,6 +46,10 @@ pub async fn build_ap_only_network(
// Metadata
ap_object.insert("type".to_string(), "AP".to_string().into());
ap_object.insert(
"id".to_string(),
format!("uisp:device:{}", ap_device.id).into(),
);
ap_object.insert("uisp_device".to_string(), ap_device.id.clone().into());
// Save the entry
@@ -204,8 +204,17 @@ impl Layer {
site.max_down_mbps.into(),
);
root.insert("uploadBandwidthMbps".to_owned(), site.max_up_mbps.into());
root.insert("id".to_string(), format!("uisp:site:{}", site.id).into());
root.insert("uisp_site".to_string(), site.id.clone().into());
root.insert("parent_site".to_string(), name.to_string().into());
if let (Some(latitude), Some(longitude)) = (site.latitude, site.longitude) {
if let Some(number) = serde_json::Number::from_f64(latitude as f64) {
root.insert("latitude".to_string(), serde_json::Value::Number(number));
}
if let Some(number) = serde_json::Number::from_f64(longitude as f64) {
root.insert("longitude".to_string(), serde_json::Value::Number(number));
}
}
}
}
GraphMapping::AccessPointByName(name) => {
@@ -215,6 +224,7 @@ impl Layer {
if let Some(device) = uisp_data.devices.iter().find(|d| d.name == *name) {
root.insert("downloadBandwidthMbps".to_owned(), device.download.into());
root.insert("uploadBandwidthMbps".to_owned(), device.upload.into());
root.insert("id".to_string(), format!("uisp:device:{}", device.id).into());
root.insert("uisp_device".to_string(), device.id.clone().into());
}
}
@@ -1,3 +1,9 @@
//! Legacy UISP full-topology builder.
//!
//! `build_full_network` is retained for legacy reference and shared support code,
//! but current strategy dispatch does not select it. The public `"full"` strategy
//! now aliases to the `full2` backend.
#![allow(dead_code)]
mod ap_promotion;
pub(crate) mod bandwidth_overrides;
@@ -115,6 +121,8 @@ pub async fn build_full_network(
id: format!("{}_Infrastructure", sites[root_idx].name.clone()),
name: format!("{}_Infrastructure", sites[root_idx].name.clone()),
site_type: UispSiteType::Site,
latitude: None,
longitude: None,
uisp_parent_id: None,
parent_indices: Default::default(),
max_down_mbps: sites[root_idx].max_down_mbps,
@@ -61,6 +61,18 @@ fn traverse_sites(
"type".to_string(),
serde_json::Value::String(sites[idx].site_type.as_network_json_string()),
);
entry.insert(
"id".to_string(),
serde_json::Value::String(generic_node_id(&sites[idx])),
);
if let (Some(latitude), Some(longitude)) = (sites[idx].latitude, sites[idx].longitude) {
if let Some(number) = serde_json::Number::from_f64(latitude as f64) {
entry.insert("latitude".to_string(), serde_json::Value::Number(number));
}
if let Some(number) = serde_json::Number::from_f64(longitude as f64) {
entry.insert("longitude".to_string(), serde_json::Value::Number(number));
}
}
if depth < 10 {
let mut children = serde_json::Map::new();
@@ -86,3 +98,59 @@ fn traverse_sites(
fn should_traverse(t: &UispSiteType) -> bool {
!matches!(t, UispSiteType::Client)
}
fn generic_node_id(site: &UispSite) -> String {
if site.site_type == UispSiteType::ClientWithChildren {
format!(
"libreqos:generated:uisp:site:{}",
slugify_generated_name(&site.name)
)
} else {
format!("uisp:site:{}", site.id)
}
}
fn slugify_generated_name(name: &str) -> String {
let mut slug = String::with_capacity(name.len());
let mut last_was_dash = false;
for ch in name.chars() {
if ch.is_ascii_alphanumeric() {
slug.push(ch.to_ascii_lowercase());
last_was_dash = false;
} else if !last_was_dash {
slug.push('-');
last_was_dash = true;
}
}
slug.trim_matches('-').to_string()
}
#[cfg(test)]
mod test {
use super::{generic_node_id, slugify_generated_name};
use crate::uisp_types::{UispSite, UispSiteType};
#[test]
fn slugifies_generated_site_names() {
assert_eq!(slugify_generated_name("Orphans"), "orphans");
assert_eq!(
slugify_generated_name("Generated Site / Tower 1"),
"generated-site-tower-1"
);
}
#[test]
fn generated_site_ids_use_slugged_names() {
let site = UispSite {
id: "ignored-for-generated".to_string(),
name: "Generated Site / Tower 1".to_string(),
site_type: UispSiteType::ClientWithChildren,
..Default::default()
};
assert_eq!(
generic_node_id(&site),
"libreqos:generated:uisp:site:generated-site-tower-1"
);
}
}
@@ -78,6 +78,8 @@ pub fn warn_of_no_parents_and_promote(
id: "orphans".to_string(),
name: "Orphaned Nodes".to_string(),
site_type: UispSiteType::Site,
latitude: None,
longitude: None,
uisp_parent_id: None,
parent_indices: Default::default(),
max_down_mbps: config.queues.downlink_bandwidth_mbps,
@@ -667,6 +667,16 @@ pub fn add_all_sites_to_graph(
let root_entry = GraphMapping::Root {
name: site_name,
id,
latitude: site
.description
.as_ref()
.and_then(|description| description.location.as_ref())
.map(|location| location.latitude as f32),
longitude: site
.description
.as_ref()
.and_then(|description| description.location.as_ref())
.map(|location| location.longitude as f32),
};
let root_ref = graph.add_node(root_entry);
*root_idx = Some(root_ref);
@@ -676,6 +686,16 @@ pub fn add_all_sites_to_graph(
let site_entry = GraphMapping::Site {
name: site_name,
id,
latitude: site
.description
.as_ref()
.and_then(|description| description.location.as_ref())
.map(|location| location.latitude as f32),
longitude: site
.description
.as_ref()
.and_then(|description| description.location.as_ref())
.map(|location| location.longitude as f32),
};
let site_ref = graph.add_node(site_entry);
site_map.insert(site.id.clone(), site_ref);
@@ -1,14 +1,18 @@
use serde::Serialize;
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Serialize)]
#[derive(Debug, PartialEq, Clone, Serialize)]
pub enum GraphMapping {
Root {
name: String,
id: String,
latitude: Option<f32>,
longitude: Option<f32>,
},
Site {
name: String,
id: String,
latitude: Option<f32>,
longitude: Option<f32>,
},
GeneratedSite {
name: String,
@@ -31,4 +35,63 @@ impl GraphMapping {
GraphMapping::AccessPoint { name, .. } => name.clone(),
}
}
pub fn network_json_id(&self) -> String {
match self {
GraphMapping::Root { id, .. } | GraphMapping::Site { id, .. } => {
format!("uisp:site:{id}")
}
GraphMapping::GeneratedSite { name } => {
format!("libreqos:generated:uisp:site:{}", slugify_generated_name(name))
}
GraphMapping::AccessPoint { id, .. } => format!("uisp:device:{id}"),
}
}
}
fn slugify_generated_name(name: &str) -> String {
let mut slug = String::with_capacity(name.len());
let mut last_was_dash = false;
for ch in name.chars() {
if ch.is_ascii_alphanumeric() {
slug.push(ch.to_ascii_lowercase());
last_was_dash = false;
} else if !last_was_dash {
slug.push('-');
last_was_dash = true;
}
}
slug.trim_matches('-').to_string()
}
#[cfg(test)]
mod test {
use super::GraphMapping;
#[test]
fn emits_generic_ids_for_real_and_generated_nodes() {
let root = GraphMapping::Root {
name: "Main".to_string(),
id: "site-1".to_string(),
latitude: None,
longitude: None,
};
let ap = GraphMapping::AccessPoint {
name: "AP".to_string(),
id: "device-1".to_string(),
site_name: "Main".to_string(),
download_mbps: 100,
upload_mbps: 100,
};
let generated = GraphMapping::GeneratedSite {
name: "Orphans".to_string(),
};
assert_eq!(root.network_json_id(), "uisp:site:site-1");
assert_eq!(ap.network_json_id(), "uisp:device:device-1");
assert_eq!(
generated.network_json_id(),
"libreqos:generated:uisp:site:orphans"
);
}
}
@@ -18,13 +18,35 @@ pub fn walk_parents(
let mut map = serde_json::Map::new();
// Entries are name, type, uisp_device or site, downloadBandwidthMbps, uploadBandwidthMbps, children
map.insert("name".into(), name.clone().into());
map.insert("id".into(), node_info.mapping.network_json_id().into());
map.insert("downloadBandwidthMbps".into(), node_info.download.into());
map.insert("uploadBandwidthMbps".into(), node_info.upload.into());
match node_info.mapping {
GraphMapping::Root { id, .. } | GraphMapping::Site { id, .. } => {
GraphMapping::Root {
id,
latitude,
longitude,
..
}
| GraphMapping::Site {
id,
latitude,
longitude,
..
} => {
map.insert("type".into(), "Site".into());
map.insert("uisp_site".into(), id.clone().into());
map.insert("parent_site".into(), name.clone().into());
if let Some(latitude) = latitude
&& let Some(number) = serde_json::Number::from_f64(*latitude as f64)
{
map.insert("latitude".into(), serde_json::Value::Number(number));
}
if let Some(longitude) = longitude
&& let Some(number) = serde_json::Number::from_f64(*longitude as f64)
{
map.insert("longitude".into(), serde_json::Value::Number(number));
}
}
GraphMapping::GeneratedSite { .. } => {
map.insert("type".into(), "Site".into());
@@ -31,7 +31,9 @@ pub async fn build_with_strategy(
flat::build_flat_network(config, ip_ranges).await?;
Ok(())
}
/*"full" => {
/* "full" is now an alias for full2. The legacy full topology builder remains
* here only as shared support code and historical reference.
* "full" => {
info!("Strategy selected: full");
full::build_full_network(config, ip_ranges).await?;
Ok(())
@@ -11,6 +11,8 @@ pub struct UispSite {
pub id: String,
pub name: String,
pub site_type: UispSiteType,
pub latitude: Option<f32>,
pub longitude: Option<f32>,
pub uisp_parent_id: Option<String>,
pub parent_indices: HashSet<usize>,
pub max_down_mbps: u64,
@@ -32,6 +34,8 @@ impl Default for UispSite {
id: "".to_string(),
name: "".to_string(),
site_type: UispSiteType::Site,
latitude: None,
longitude: None,
uisp_parent_id: None,
parent_indices: Default::default(),
max_down_mbps: 0,
@@ -70,6 +74,12 @@ impl UispSite {
config.queues.generated_pn_download_mbps,
config.queues.generated_pn_upload_mbps,
);
let (latitude, longitude) = value
.description
.as_ref()
.and_then(|description| description.location.as_ref())
.map(|location| (Some(location.latitude as f32), Some(location.longitude as f32)))
.unwrap_or((None, None));
// Extract UISP QoS base speeds (bps -> Mbps) and burst sizes (kB/s -> Mbps)
let mut base_down_mbps: f32 = 0.0;
let mut base_up_mbps: f32 = 0.0;
@@ -118,6 +128,8 @@ impl UispSite {
id: value.id.clone(),
name: value.name_or_blank(),
site_type: UispSiteType::from_uisp_record(value).unwrap(),
latitude,
longitude,
parent_indices: HashSet::new(),
uisp_parent_id,
max_down_mbps,
+35
View File
@@ -256,6 +256,41 @@ class TestGraph(unittest.TestCase):
exampleFile = json.load(file)
self.assertEqual(newFile, exampleFile)
def test_network_json_writes_optional_node_ids(self):
from integrationCommon import NetworkGraph, NetworkNode, NodeType
import json
net = NetworkGraph()
net.addRawNode(
NetworkNode(
"Site_1",
"Site_1",
"",
NodeType.site,
1000,
1000,
networkJsonId="sonar:site:123",
)
)
net.addRawNode(
NetworkNode(
"AP_1",
"AP_1",
"Site_1",
NodeType.ap,
500,
500,
networkJsonId="sonar:ap:456",
)
)
net.prepareTree()
net.createNetworkJson()
with open('network.json') as file:
newFile = json.load(file)
self.assertEqual(newFile["Site_1"]["id"], "sonar:site:123")
self.assertEqual(newFile["Site_1"]["children"]["AP_1"]["id"], "sonar:ap:456")
def test_ipv4_to_ipv6_map(self):
"""
Tests the underlying functionality of finding an IPv6 address from an IPv4 mapping