# BeeLifeMaps / CropsMap - Technical Guide

## What is BeeLifeMaps?

BeeLifeMaps is a web-based geospatial tool for **apicultural site analysis**. It helps beekeepers find optimal locations for beehives by analyzing agricultural parcels, land use, vegetation, and seasonal flowering patterns.

The application overlays official government agricultural data (parcel boundaries, crop types, land use) on an interactive map and runs scoring algorithms to recommend the best zones for beekeeping.

---

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────┐
│  BROWSER (Leaflet Map)                                      │
│  index.html + analysis_{country}.js                         │
│                                                             │
│  Country: ES → analysis_real.js (SIGPAC WMS)                │
│  Country: SE → analysis_sweden.js (Jordbruksverket WFS)     │
│  Country: XX → analysis_{xx}.js (future)                    │
└────────────────┬────────────────────────────────────────────┘
                 │  HTTP requests
                 ▼
┌─────────────────────────────────────────────────────────────┐
│  BACKEND (Express.js - port 3996)                           │
│  server/index.js                                            │
│                                                             │
│  /proxy?url=...     → CORS proxy for WMS/WFS servers        │
│  /dw-map            → Google Earth Engine Dynamic World      │
│  /sigpac-capabilities → WMS GetCapabilities                  │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│  EXTERNAL SERVICES                                          │
│                                                             │
│  Spain:   wms.mapa.gob.es/sigpac/wms (WMS 1.3.0)           │
│  Sweden:  epub.sjv.se/inspire/inspire/wms (WMS 1.3.0)      │
│           epub.sjv.se/inspire/inspire/wfs (WFS 2.0.0)      │
│  Earth Engine: GOOGLE/DYNAMICWORLD/V1                       │
│  Geocoding: Nominatim (OpenStreetMap)                       │
└─────────────────────────────────────────────────────────────┘
```

**Files:**

| File | Purpose |
|------|---------|
| `web/index.html` | UI, map, controls, country switching, click-on-parcel popups |
| `web/analysis_real.js` | Spain: SIGPAC queries, scoring, quick & deep analysis |
| `web/analysis_sweden.js` | Sweden: Jordbruksverket queries, scoring, quick & deep analysis |
| `server/index.js` | Express backend: CORS proxy, Earth Engine tiles |

---

## Features per Country (Spain = Reference)

Spain is the **reference implementation**. Every new country must implement these same features:

### 1. WMS Tile Layers (visual parcel boundaries on map)

Toggle buttons that overlay official agricultural parcel boundaries on the map as semi-transparent tile layers.

| Feature | Spain | Sweden |
|---------|-------|--------|
| Main parcel layer | SIGPAC parcels (`AU.Sigpac:recinto`) | Jordbruksblock (`inspire:senaste_arslager_block`) |
| Secondary layer | Declared Crops (`AU.Sigpac:linea_declaracion`) | Jordbruksskifte (`inspire:senaste_arslager_skifte`) |
| WMS URL | `https://wms.mapa.gob.es/sigpac/wms` | `http://epub.sjv.se/inspire/inspire/wms` |
| Toggle buttons | "Show/Hide SIGPAC", "Show/Hide Declared Crops" | "Show/Hide Jordbruksblock", "Show/Hide Jordbruksskifte" |

### 2. Click-on-Parcel Popup (identify parcel info)

When a WMS layer is visible and the user clicks on the map, a popup appears showing parcel details.

| Feature | Spain | Sweden |
|---------|-------|--------|
| Query method | WMS GetFeatureInfo (returns HTML) | WFS GetFeature with small BBOX (returns GeoJSON) |
| Response format | HTML table (needs parsing) | GeoJSON (direct JSON access) |
| Key fields shown | Provincia, Municipio, Poligono, Parcela, Recinto, Superficie, Uso SIGPAC | Block ID, Kategori, Agoslag, Areal, Region kod, Year |
| Popup CSS class | `sigpac-popup` | `sweden-popup` |

### 3. Quick Exploration (top 6 locations)

Button "Quick Exploration" fetches parcels in the visible area and shows the 6 best-scoring locations with markers.

| Feature | Spain | Sweden |
|---------|-------|--------|
| Function | `findBeeLocationsByGeography()` | `findBeeLocationsSwedenByGeography()` |
| Data source | SIGPAC WMS GetFeatureInfo (500 sample points) | Jordbruksverket WFS GetFeature (paginated) |
| Filter threshold | Top 6 by score | valorApicola > 0.4, then top 6 with spacing |
| Minimum spacing | radiusKm * 0.15 | radiusKm * 0.15 |
| Cache | `exploracionRapidaCache` | `swedenExploracionRapidaCache` |

### 4. Deep Zone Analysis (top 10 zones)

Button "Apicultural Zone Analysis" performs a comprehensive grid-based analysis and recommends the top 10 zones.

| Feature | Spain | Sweden |
|---------|-------|--------|
| Function | `performDeepAnalysis()` | `performDeepAnalysisSweden()` |
| Grid cell size | 0.01° (~1.1km) | 0.009° (~1km at Swedish latitudes) |
| Min score filter | - | valorApicola > 0.3 |
| Top zones shown | 10 | 10 |
| Zone popup content | Score, ranking, hive density, crop breakdown, ROI estimates | Score, ranking, hive density, land type breakdown, seasonal advice |

### 5. Recommended Zones Filter

Button "Recommended Zones Only" toggles visibility of all markers except the top recommended zones.

### 6. Scoring System

Each country has its own `calculateValorApicola()` function and crop phenology table.

---

## How to Add a New Country

### Step 1: Research the Country's Agricultural Data Service

Find the national WMS/WFS service that publishes agricultural parcel data. Common sources:

- **INSPIRE directive** (EU countries): Most EU countries publish under INSPIRE
- **National agricultural agencies**: Search for "agricultural parcels WMS" or "LPIS WFS" + country name
- Check: https://inspire-geoportal.ec.europa.eu/

You need to discover:

1. **WMS endpoint URL** — for tile layer visualization
2. **WFS endpoint URL** — for feature queries (GeoJSON)
3. **Layer names** — run GetCapabilities to find them
4. **Coordinate system** — typically EPSG:4326 or EPSG:3857
5. **Available properties** — what fields each parcel has (crop type, area, ID, etc.)
6. **BBOX parameter order** — varies by server!

#### How to Discover Layers

```
# WMS GetCapabilities (find layer names)
curl "https://{wms-url}?service=WMS&request=GetCapabilities"

# WFS GetCapabilities (find feature type names)
curl "https://{wfs-url}?service=WFS&request=GetCapabilities"

# WFS sample features (see available properties)
curl "https://{wfs-url}?service=WFS&version=2.0.0&request=GetFeature&typeNames={layerName}&outputFormat=application/json&count=5"
```

#### Critical: Test BBOX Order

Different servers expect different BBOX parameter orders. Test with a known coordinate:

```
# Try west,south,east,north (most common for EPSG:4326)
bbox=15.0,58.0,16.0,59.0,EPSG:4326

# Try south,west,north,east (some servers)
bbox=58.0,15.0,59.0,16.0,EPSG:4326
```

If you get 0 features with one order, try the other.

### Step 2: Test WMS Tile Rendering

Verify tiles render correctly before coding:

```
https://{wms-url}?service=WMS&version=1.3.0&request=GetMap
  &layers={layerName}
  &styles=
  &format=image/png
  &transparent=true
  &width=256&height=256
  &crs=EPSG:3857
  &bbox={x1},{y1},{x2},{y2}
```

If you get a valid PNG image with visible parcels, the WMS works.

### Step 3: Create the Analysis Module

Create `web/analysis_{countrycode}.js` following this template:

```javascript
// ============================================================================
// {COUNTRY} Agricultural Parcel Analysis Module
// Handles {Agency} WFS data for beekeeping site analysis
// ============================================================================

// 1. CROP PHENOLOGY TABLE
// Monthly floral index (0.0-1.0) for crops in this country
const {COUNTRY}_CROP_PHENOLOGY = {
  'crop_name': [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec],
  // ... more crops
};

// 2. LAND TYPE BASE VALUES
// Apicultural value (0.0-1.0) for each land use category
const {COUNTRY}_LANDTYPE_VALUES = {
  'land_type_code': 0.85,  // description
  // ... more types
};

// 3. LAND TYPE ICONS
function get{Country}LandIcon(landType) {
  const icons = { 'type': '🌾', ... };
  return icons[landType] || '📍';
}

// 4. SCORING FUNCTION
function calculateValorApicola{Country}(landType, month) {
  // Base value from land type
  // Seasonal adjustment from phenology
  // Country-specific bonuses/penalties
  // Return 0.0-1.0
}

// 5. CENTROID CALCULATION
function calculateCentroid{Country}(geometry) {
  // Handle Point, Polygon, MultiPolygon GeoJSON types
  // Return { lat, lng }
}

// 6. DATA FETCHING
async function getAllParcelas{Country}(center, radiusKm) {
  // Calculate BBOX from center + radius
  // Query WFS with pagination
  // Parse GeoJSON features
  // Extract properties (ID, land type, area, etc.)
  // Calculate centroid from geometry
  // Calculate valorApicola for each parcel
  // Cache results
  // Return array of parcel objects
}

// 7. QUICK EXPLORATION
async function findBeeLocations{Country}ByGeography() {
  // Get center from currentCircle or map bounds
  // Call getAllParcelas{Country}()
  // Filter by valorApicola > threshold
  // Select top 6 with spacing
  // Show markers with popups
  // Cache results
}

// 8. DEEP ZONE ANALYSIS
async function performDeepAnalysis{Country}() {
  // Get center from currentCircle or map bounds
  // Step 1: Fetch all parcels
  // Step 2: Visualize all parcels
  // Step 3: Grid aggregation
  // Step 4: Score and rank zones
  // Step 5: Show top 10 zone markers with detailed popups
}

// 9. VISUALIZATION
function visualizeParcelas{Country}(parcelas) {
  // Create markers for each parcel
  // Color by score
  // Bind popups with parcel details
}
```

### Step 4: Modify index.html

#### 4a. Add Country Option

```html
<select id="country-select">
  <option value="ES">ES</option>
  <option value="SE">SE</option>
  <option value="XX">XX</option>  <!-- NEW -->
</select>
```

#### 4b. Add WMS Layer Variables

```javascript
// {Country} WMS layers (initially hidden)
const {COUNTRY}_WMS = 'https://{wms-url}';
let {country}Layer1 = L.tileLayer.wms({COUNTRY}_WMS, {
  layers: '{layerName1}',
  format: 'image/png',
  transparent: true,
  version: '1.3.0',
  attribution: '© {Agency}',
  opacity: 0,
  zIndex: 500
}).addTo(map);
```

#### 4c. Add Toggle Buttons Row

```html
<div class="tools-row" id="tools-row-{country}" style="display:none;">
  <button id="toggle-{layer1}">Show {Layer1}</button>
  <button id="toggle-{layer2}">🌾 Show {Layer2}</button>
</div>
```

#### 4d. Add Country Switching Logic

In the country selector change handler, for each country:
- Set map center and zoom
- Show/hide the correct tools-row
- Remove irrelevant WMS layers from map (prevents tile errors for wrong region)
- Re-add relevant WMS layers
- Reset toggle states

```javascript
if (selectedCountry === 'XX') {
  qInput.placeholder = 'Search location (e.g. {city})';
  map.setView([{lat}, {lng}], {zoom});
  document.getElementById('tools-row-spain').style.display = 'none';
  document.getElementById('tools-row-sweden').style.display = 'none';
  document.getElementById('tools-row-{country}').style.display = 'flex';
  setAllSigpacLayersVisible(false);
  setAllSwedishLayersVisible(false);
  // Add new country layers to map
}
```

**Important**: Always **remove** (not just hide) WMS layers from other countries when switching. Setting opacity to 0 still causes Leaflet to request tiles from those servers, generating console errors.

#### 4e. Add Toggle Event Listeners

```javascript
document.getElementById('toggle-{layer1}').addEventListener('click', () => {
  show{Layer1} = !show{Layer1};
  {layer1}Layer.setOpacity(show{Layer1} ? 0.7 : 0);
  document.getElementById('toggle-{layer1}').textContent =
    show{Layer1} ? 'Hide {Layer1}' : 'Show {Layer1}';
});
```

#### 4f. Add Click-on-Parcel Handler

In the `map.on('click')` handler, add a branch for the new country. Two approaches depending on available API:

**If WFS is available (preferred — returns GeoJSON):**

```javascript
if (is{Country}Active) {
  const lat = e.latlng.lat, lng = e.latlng.lng;
  const delta = 0.0005; // ~50m buffer
  const bbox = `${lng-delta},${lat-delta},${lng+delta},${lat+delta},EPSG:4326`;

  const wfsUrl = `${WFS_URL}?service=WFS&version=2.0.0&request=GetFeature` +
    `&typeNames={layerName}&outputFormat=application/json` +
    `&srsName=EPSG:4326&count=10&bbox=${bbox}`;

  const proxyUrl = `${BACKEND}/proxy?url=${encodeURIComponent(wfsUrl)}`;
  const data = await (await fetch(proxyUrl)).json();

  // Build popup from data.features[].properties
}
```

**If only WMS GetFeatureInfo is available (returns HTML):**

```javascript
if (is{Country}Active) {
  const size = map.getSize();
  const bounds = map.getBounds();
  const sw = L.CRS.EPSG3857.project(bounds.getSouthWest());
  const ne = L.CRS.EPSG3857.project(bounds.getNorthEast());
  const bbox = [sw.x, sw.y, ne.x, ne.y].join(',');
  const point = map.latLngToContainerPoint(e.latlng);

  const params = new URLSearchParams({
    service: 'WMS', version: '1.3.0', request: 'GetFeatureInfo',
    layers: '{layerName}', query_layers: '{layerName}',
    info_format: 'text/html',
    i: Math.round(point.x), j: Math.round(point.y),
    crs: 'EPSG:3857', bbox: bbox,
    width: size.x, height: size.y
  });

  const url = `${BACKEND}/proxy?url=${encodeURIComponent(WMS_URL + '?' + params)}`;
  const html = await (await fetch(url)).text();
  // Parse HTML and show popup
}
```

#### 4g. Wire Quick Exploration and Deep Analysis

In the existing handlers, add the new country branch:

```javascript
// Quick Exploration handler
if (selectedCountry === 'XX') {
  await findBeeLocations{Country}ByGeography();
}

// Deep Analysis handler
if (selectedCountry === 'XX') {
  await performDeepAnalysis{Country}();
}
```

#### 4h. Load the Script

```html
<script src="analysis_{country}.js"></script>
```

### Step 5: No Backend Changes Needed

The proxy at `/proxy?url=...` is generic — it works with any HTTP URL. No backend modifications are required when adding a new country.

---

## Country-Specific Technical Notes

### Spain (ES) — SIGPAC

- **Agency**: MAPA (Ministerio de Agricultura, Pesca y Alimentación)
- **WMS**: `https://wms.mapa.gob.es/sigpac/wms` (WMS 1.3.0)
- **WFS**: Not used directly; data is queried via WMS GetFeatureInfo
- **Query method**: WMS GetFeatureInfo returns **HTML tables** that must be parsed
- **Projection**: EPSG:3857 for GetFeatureInfo requests
- **Key layers**:
  - `AU.Sigpac:recinto` — Parcel boundaries
  - `AU.Sigpac:linea_declaracion` — Declared crops (specific crop data)
  - `AU.Sigpac:e_paisaje` — Landscape elements
  - `AU.Sigpac:e_paisaje_linea` — Natural refuges
  - `AU.Sigpac:e_paisaje_punto` — Point features
- **Key fields**: provincia, municipio, poligono, parcela, recinto, uso_sigpac, superficie, coef_regadio
- **Crop codes**: Two-letter codes (CI=citrus, OV=olive, VI=vineyard, TA=arable, etc.)
- **Quirks**:
  - Server goes down occasionally (503 errors) — nothing can be done except wait
  - GetFeatureInfo returns HTML, not JSON — requires HTML parsing
  - For analysis, 500 sample points are generated and each queried individually (slow but the only option without WFS)
  - Default map center: [40.4637, -3.7492] (Madrid), zoom 6

### Sweden (SE) — Jordbruksverket

- **Agency**: Jordbruksverket (Swedish Board of Agriculture)
- **WMS**: `http://epub.sjv.se/inspire/inspire/wms` (WMS 1.3.0)
- **WFS**: `http://epub.sjv.se/inspire/inspire/wfs` (WFS 2.0.0)
- **Query method**: WFS GetFeature returns **GeoJSON** directly (much simpler than Spain)
- **Projection**: EPSG:4326 (WGS84 decimal degrees)
- **BBOX order**: `west,south,east,north,EPSG:4326`
- **Key layers**:
  - `inspire:senaste_arslager_block` — Agricultural blocks
  - `inspire:senaste_arslager_skifte` — Agricultural parcels (skiften)
- **Key fields**: blockid, skifteid, agoslag, areal, region_kod, kategori, arslager
- **Land type field**: `agoslag` (Swedish land use classification)
- **Pagination**: WFS supports `count` and `startIndex` parameters (500 per page)
- **Quirks**:
  - Uses HTTP (not HTTPS) — `http://epub.sjv.se/...`
  - BBOX order was initially wrong — must be `west,south,east,north` (not south,west,north,east)
  - WFS returns full polygon geometries — centroid must be calculated client-side
  - Midnight sun bonus in scoring: latitudes above 60°N get 15% boost May-July
  - Default map center: [62.0, 15.0], zoom 5

### Italy (IT) — Hybrid: National Cadastral + Regional WFS

- **Agencies**: Agenzia delle Entrate (cadastral), Regione Emilia-Romagna (land use)
- **No national agricultural WMS/WFS exists** — AGEA/SIAN does not expose parcels publicly
- **WMS (national)**: `https://wms.cartografia.agenziaentrate.gov.it/inspire/wms/ows01.php` (WMS 1.3.0)
- **WFS (Emilia-Romagna only)**: `http://servizigis.regione.emilia-romagna.it/wfs/uso_del_suolo` (WFS 2.0.0)
- **Cadastral layer**: `CP.CadastralParcel` — shows parcel boundaries, IDs (no crop data)
- **Land use layer**: `_020_uso_suolo_ed2023` — detailed land use with descriptions
- **Key fields (WFS)**: SIGLA, COD_TOT, DESCR, HECTARES, SHAPE.AREA
- **WFS outputFormat**: Must be `geojson` (NOT `application/json` — server rejects it)
- **Projection**: Cadastral WMS needs EPSG:3857 for GetFeatureInfo; WFS supports EPSG:4326
- **Quirks**:
  - Analysis only works in Emilia-Romagna region (lat 43.7-45.15, lng 9.2-12.8)
  - Cadastral WMS rejects GetFeatureInfo with EPSG:4326 — must use EPSG:3857
  - Mediterranean climate bonus in scoring: April-September × 1.1
  - Default map center: [44.5, 11.3] (Bologna), zoom 8
  - Full research documented in `ITALY_RESEARCH.md`

---

## Scoring System Design

Each country's scoring function follows this pattern:

```
valorApicola = baseValue(landType) × seasonalAdjustment(month) × countryBonus
```

### Components:

1. **Base Value (0.0-1.0)**: How good is this land type for bees?
   - Flowering crops (rapeseed, clover, citrus) → 0.8-1.0
   - Pastures, meadows → 0.6-0.8
   - Forests → 0.3-0.6
   - Cereals (wind-pollinated) → 0.1-0.2
   - Urban, water, bare → 0.0-0.1

2. **Seasonal Adjustment**: Monthly floral index from phenology table
   - Multiply base value by `(0.4 + floralIndex * 0.6)` or similar
   - Ensures scoring reflects current month's flowering

3. **Country-Specific Bonuses**:
   - Spain: Irrigation bonus (regadio × 1.2)
   - Sweden: Midnight sun bonus (May-Jul × 1.15), winter penalty (Nov-Feb × 0.1)

### Phenology Table Format

```javascript
const CROP_PHENOLOGY = {
  'crop_name': [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec],
};
```

Each value is 0.0 (no flowering) to 1.0 (peak flowering). Research local crop calendars and bee forage guides for your country.

---

## Proxy Server

The backend proxy (`/proxy?url=...`) is essential because browsers block cross-origin requests to government WMS/WFS servers.

**How it works:**
1. Frontend encodes the full WMS/WFS URL and passes it as `?url=` parameter
2. Backend fetches the URL server-side (no CORS restriction)
3. Backend returns the response with `Access-Control-Allow-Origin: *`

**Double encoding is normal**: `URLSearchParams.toString()` encodes commas as `%2C`, then `encodeURIComponent()` encodes the `%` as `%25`, resulting in `%252C` in the browser URL. Express decodes this automatically.

The proxy is generic and works with any country's WMS/WFS server without modification.

---

## Common Issues & Solutions

| Problem | Cause | Solution |
|---------|-------|----------|
| 503 Service Unavailable | Government WMS/WFS server is down | Wait and retry later; nothing to fix in code |
| 0 features returned | Wrong BBOX order | Try `west,south,east,north` vs `south,west,north,east` |
| Console tile errors for wrong country | WMS layers still on map when viewing another country | Use `map.removeLayer()` not just `setOpacity(0)` |
| CORS errors | Direct browser requests to WMS/WFS | All requests must go through the `/proxy` endpoint |
| Quick Exploration finds nothing | Zoomed out too far, center is in wilderness | Calculate radius from visible map bounds, not fixed 5km |
| HTML response instead of JSON | WMS GetFeatureInfo returns HTML | Parse HTML tables, or use WFS instead if available |
| GeoJSON with no properties | Wrong layer name or version | Verify with GetCapabilities and sample GetFeature |

---

## Checklist for Adding a New Country

- [ ] Find WMS endpoint and verify tiles render
- [ ] Find WFS endpoint (preferred) or confirm GetFeatureInfo works
- [ ] Identify layer names via GetCapabilities
- [ ] Test BBOX order with known coordinates
- [ ] Identify property field names (land type, area, ID, etc.)
- [ ] Create `analysis_{country}.js` with phenology table, scoring, and all functions
- [ ] Add country option to `<select id="country-select">`
- [ ] Add WMS layer variables in index.html
- [ ] Add tools-row with toggle buttons
- [ ] Add country switching logic (show/hide layers, set map center)
- [ ] Add toggle event listeners
- [ ] Add click-on-parcel popup handler
- [ ] Wire Quick Exploration and Deep Analysis to new functions
- [ ] Load the new script in index.html
- [ ] Test: WMS tiles visible at appropriate zoom
- [ ] Test: Click popup shows parcel data
- [ ] Test: Quick Exploration finds locations
- [ ] Test: Deep Analysis generates zone recommendations
- [ ] Test: No console errors from other countries' layers
