MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
Eloise Zomia (talk | contribs) No edit summary |
Eloise Zomia (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
/* Landrace.wiki – | /* Landrace.wiki – Leaflet map with SMW Ask API support (Conservation Priority) */ | ||
(function () { | (function () { | ||
var LVER = '1.9.4'; | var LVER = '1.9.4'; | ||
| Line 7: | Line 7: | ||
if (id && document.getElementById(id)) return; | if (id && document.getElementById(id)) return; | ||
var l = document.createElement('link'); | var l = document.createElement('link'); | ||
l.rel = 'stylesheet'; l.href = href; if (id) l.id = id; | l.rel = 'stylesheet'; | ||
l.href = href; | |||
if (id) l.id = id; | |||
document.head.appendChild(l); | document.head.appendChild(l); | ||
} | } | ||
function addJS(src, cb) { | function addJS(src, cb) { | ||
if (window.L) return cb(); | if (window.L && typeof window.L.map === 'function') return cb(); | ||
var existing = document.querySelector('script[data-lw-leaflet="1"]'); | |||
if (existing) { | |||
existing.addEventListener('load', cb); | |||
return; | |||
} | |||
var s = document.createElement('script'); | var s = document.createElement('script'); | ||
s.src = src; | s.src = src; | ||
s.onload = cb; | s.setAttribute('data-lw-leaflet', '1'); | ||
s.onerror = function(){ console.error('Leaflet failed to load:', src); }; | s.onload = cb; | ||
s.onerror = function () { console.error('Leaflet failed to load:', src); }; | |||
document.head.appendChild(s); | document.head.appendChild(s); | ||
} | } | ||
function | function esc(s) { | ||
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { | |||
return ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]); | |||
}); | |||
} | |||
// Conservation Priority colors: Critical / High / Medium / Low | |||
} | function statusColor(s) { | ||
return ({ | |||
function | 'Critical': '#e74c3c', | ||
'High': '#e67e22', | |||
'Medium': '#f1c40f', | |||
'Low': '#2ecc71' | |||
})[s] || '#3498db'; | |||
} | |||
function popupForAccession(p) { | |||
var title = [p.id, p.name].filter(Boolean).join(' — '); | |||
var status = p.status ? esc(p.status) : ''; | |||
var link = p.page_url ? '<br><a href="' + esc(p.page_url) + '">Open accession</a>' : ''; | |||
return '<b>' + esc(title) + '</b>' + (status ? '<br>' + status : '') + link; | |||
} | } | ||
// Convert SMW API | // Convert SMW Ask API response to GeoJSON FeatureCollection | ||
function smwToGeoJSON(data) { | function smwToGeoJSON(data) { | ||
var features = []; | var features = []; | ||
var results = data.query ? data.query.results : data.results; | var results = (data && data.query && data.query.results) ? data.query.results : (data && data.results ? data.results : null); | ||
if (!results) { | if (!results) { | ||
console.log('No results in SMW response'); | console.log('[lw-map] No results in SMW response'); | ||
return { type: 'FeatureCollection', features: [] }; | return { type: 'FeatureCollection', features: [] }; | ||
} | } | ||
for (var pageName in results) { | for (var pageName in results) { | ||
var item = results[pageName]; | if (!Object.prototype.hasOwnProperty.call(results, pageName)) continue; | ||
var item = results[pageName] || {}; | |||
var printouts = item.printouts || {}; | var printouts = item.printouts || {}; | ||
var coordsArr = printouts['Has GPS coordinates']; | |||
var | if (!coordsArr || !coordsArr.length) continue; | ||
if (! | |||
var coord = coordsArr[0]; | |||
var coord = | var lat = null, lon = null; | ||
var lat, lon; | |||
// SMW coords often come back as an object with lat/lon | |||
// | if (coord && typeof coord === 'object') { | ||
if (typeof coord === 'object') { | |||
lat = coord.lat; | lat = coord.lat; | ||
lon = coord.lon | lon = coord.lon != null ? coord.lon : (coord.lng != null ? coord.lng : coord.long); | ||
} else if (typeof coord === 'string') { | } else if (typeof coord === 'string') { | ||
// Try | // Try "lat, lon" | ||
var parts = coord.split(','); | var parts = coord.split(','); | ||
if (parts.length === 2) { | if (parts.length === 2) { | ||
| Line 71: | Line 85: | ||
} | } | ||
} | } | ||
if ( | if (lat == null || lon == null || isNaN(lat) || isNaN(lon)) { | ||
console.log('Invalid coordinates for', pageName, coord); | console.log('[lw-map] Invalid coordinates for', pageName, coord); | ||
continue; | continue; | ||
} | } | ||
// | // Name | ||
var | var nameArr = printouts['Has descriptive name']; | ||
name = ( | var name = (nameArr && nameArr.length) ? nameArr[0] : pageName; | ||
var | // Conservation Priority | ||
status = ( | var statusArr = printouts['Has conservation priority']; | ||
var status = (statusArr && statusArr.length) ? statusArr[0] : ''; | |||
var | |||
// Accession ID | |||
var idArr = printouts['Has accession ID']; | |||
var idVal = (idArr && idArr.length) ? idArr[0] : ''; | |||
if (typeof | if (idVal && typeof idVal === 'object' && idVal.fulltext) idVal = idVal.fulltext; | ||
// Page URL (prefer SMW fullurl, otherwise MW util) | |||
var pageUrl = item.fullurl || (window.mw && mw.util && mw.util.getUrl ? mw.util.getUrl(pageName) : ('/wiki/' + encodeURIComponent(pageName))); | |||
features.push({ | features.push({ | ||
type: 'Feature', | type: 'Feature', | ||
properties: { | properties: { | ||
id: | id: idVal || '', | ||
name: name, | name: name || pageName, | ||
status: status, | status: status || '', | ||
page_url: | page_url: pageUrl | ||
}, | }, | ||
geometry: { | geometry: { | ||
| Line 106: | Line 121: | ||
}); | }); | ||
} | } | ||
console.log('Converted', features.length, 'features from SMW'); | console.log('[lw-map] Converted', features.length, 'features from SMW'); | ||
return { type: 'FeatureCollection', features: features }; | return { type: 'FeatureCollection', features: features }; | ||
} | } | ||
function fetchSMW(askQuery, callback) { | |||
function fetchSMW( | if (!window.mw || !mw.config) { | ||
callback(new Error('MediaWiki config not available'), null); | |||
return; | |||
} | |||
var apiUrl = mw.config.get('wgScriptPath') + '/api.php'; | var apiUrl = mw.config.get('wgScriptPath') + '/api.php'; | ||
var params = new URLSearchParams({ | var params = new URLSearchParams({ | ||
action: 'ask', | action: 'ask', | ||
query: | query: askQuery, | ||
format: 'json' | format: 'json' | ||
}); | }); | ||
console.log('Fetching SMW query:', | console.log('[lw-map] Fetching SMW query:', askQuery); | ||
fetch(apiUrl + '?' + params.toString()) | fetch(apiUrl + '?' + params.toString()) | ||
.then(function(r) { | .then(function (r) { | ||
if (!r.ok) throw new Error('SMW API error: ' + r.status); | if (!r.ok) throw new Error('SMW API error: ' + r.status); | ||
return r.json(); | return r.json(); | ||
}) | }) | ||
.then(function(data) { | .then(function (data) { | ||
var geojson = smwToGeoJSON(data); | var geojson = smwToGeoJSON(data); | ||
callback(null, geojson); | callback(null, geojson); | ||
}) | }) | ||
.catch(function(err) { | .catch(function (err) { | ||
console.error('SMW fetch error:', err); | console.error('[lw-map] SMW fetch error:', err); | ||
callback(err, null); | callback(err, null); | ||
}); | }); | ||
} | } | ||
function | function buildLegendControl() { | ||
var legend = L.control({ position: 'bottomleft' }); | |||
var legend = L.control({ position:'bottomleft' }); | |||
legend.onAdd = function () { | legend.onAdd = function () { | ||
var div = L.DomUtil.create('div', 'lw-legend-map'); | var div = L.DomUtil.create('div', 'lw-legend-map'); | ||
div.style.cssText = | |||
div.style.cssText = | |||
'background: white;' + | 'background: white;' + | ||
'padding: 12px;' + | 'padding: 12px;' + | ||
| Line 168: | Line 168: | ||
'font-size: 12px;' + | 'font-size: 12px;' + | ||
'line-height: 1.4;'; | 'line-height: 1.4;'; | ||
var html = '<div style="font-weight: | var html = '<div style="font-weight:700;margin-bottom:8px;color:#333;">Conservation Priority</div>'; | ||
var items = [ | var items = [ | ||
[' | ['Critical', '#e74c3c'], | ||
[' | ['High', '#e67e22'], | ||
['Medium', '#f1c40f'], | |||
[' | ['Low', '#2ecc71'] | ||
[' | |||
]; | ]; | ||
items.forEach(function( | items.forEach(function (it) { | ||
html += | |||
'<div style="display:flex;align-items:center;margin:6px 0;">' + | |||
html += | '<div style="width:12px;height:12px;background:' + it[1] + ';border-radius:50%;margin-right:8px;border:1px solid rgba(0,0,0,0.2);"></div>' + | ||
'<div style="display: flex; align-items: center; margin: 6px 0;">' + | '<span style="color:#555;">' + esc(it[0]) + '</span>' + | ||
'<div style=" | |||
'<span style="color: #555;">' + | |||
'</div>'; | '</div>'; | ||
}); | }); | ||
div.innerHTML = html; | div.innerHTML = html; | ||
return div; | return div; | ||
}; | }; | ||
legend.addTo(map); | return legend; | ||
} | |||
function initOne(el) { | |||
if (el.dataset.init) return; | |||
el.dataset.init = '1'; | |||
if (el.clientHeight < 100) el.style.height = '70vh'; | |||
var map = L.map(el, { minZoom: 2, maxZoom: 15, zoomControl: true }); | |||
map.setView([15, 105], 5); | |||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OSM' }).addTo(map); | |||
buildLegendControl().addTo(map); | |||
var layers = []; | var layers = []; | ||
var | var done = 0; | ||
var | var expected = 0; | ||
// | // External/inline sources (optional) | ||
['regions','populations','accessions'].forEach(function( | ['regions', 'populations', 'accessions'].forEach(function (k) { | ||
if (el.dataset[ | if (el.dataset[k]) expected++; | ||
}); | }); | ||
function | // SMW query source (optional) | ||
if (el.dataset.smwQuery) expected++; | |||
function finishOne() { | |||
done++; | |||
if (done >= expected) fitToDataOrDefault(); | |||
} | |||
function fitToDataOrDefault() { | |||
try { | try { | ||
if (layers.length | if (layers.length) { | ||
var group = L.featureGroup(layers); | var group = L.featureGroup(layers); | ||
var b = group.getBounds(); | var b = group.getBounds(); | ||
if (b && b.isValid()) { | if (b && b.isValid()) { | ||
map.fitBounds(b.pad(0.2), { | map.fitBounds(b.pad(0.2), { maxZoom: 10 }); | ||
} else { | |||
map.setView([15, 105], 5); | |||
} | } | ||
} else { | |||
map.setView([15, 105], 5); | |||
} | } | ||
} catch (e) { | } catch (e) { | ||
console.error(' | console.error('[lw-map] fit error:', e); | ||
map.setView([15, 105], 5); | map.setView([15, 105], 5); | ||
} | } | ||
setTimeout(function(){ | setTimeout(function () { map.invalidateSize(true); }, 100); | ||
} | } | ||
function | function addGeoJSON(kind, g) { | ||
if (!g || !g.features || !g.features.length) { | |||
console.log('[lw-map] No features for', kind); | |||
finishOne(); | |||
console.log('No features | |||
return; | return; | ||
} | } | ||
var layer = L.geoJSON(g, | var layer = L.geoJSON(g, { | ||
onEachFeature: function(f, ly) { | style: function (f) { | ||
var p = f.properties | return { | ||
ly.bindPopup( | color: statusColor((f.properties || {}).status), | ||
var | weight: 2, | ||
if ( | fillOpacity: 0.2, | ||
opacity: 0.8 | |||
}; | |||
}, | |||
pointToLayer: function (f, ll) { | |||
return L.circleMarker(ll, { | |||
radius: 5, | |||
color: '#fff', | |||
weight: 2, | |||
fillColor: statusColor((f.properties || {}).status), | |||
fillOpacity: 0.9 | |||
}); | |||
}, | |||
onEachFeature: function (f, ly) { | |||
var p = (f && f.properties) ? f.properties : {}; | |||
ly.bindPopup(popupForAccession(p)); | |||
var tip = p.id || p.name || ''; | |||
if (tip) ly.bindTooltip(esc(tip), { direction: 'top', offset: [0, -8], opacity: 0.9 }); | |||
} | } | ||
} | }).addTo(map); | ||
layers.push(layer); | layers.push(layer); | ||
finishOne(); | |||
} | } | ||
function loadData(kind, data) { | function loadData(kind, data) { | ||
var trimmed = data.trim(); | var trimmed = (data || '').trim(); | ||
if (!trimmed) { finishOne(); return; } | |||
// Inline JSON | |||
if (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') { | if (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') { | ||
try { | try { | ||
var | var parsed = JSON.parse(trimmed); | ||
if (Array.isArray( | if (Array.isArray(parsed)) parsed = { type: 'FeatureCollection', features: parsed }; | ||
addGeoJSON(kind, parsed); | |||
} catch (e) { | } catch (e) { | ||
console.error('[ | console.error('[lw-map] JSON parse error for', kind, e); | ||
finishOne(); | |||
} | } | ||
return; | |||
} | } | ||
// URL fetch | |||
fetch(trimmed) | |||
.then(function (r) { if (!r.ok) throw new Error(kind + ' fetch ' + r.status); return r.json(); }) | |||
.then(function (json) { addGeoJSON(kind, json); }) | |||
.catch(function (err) { console.error('[lw-map] Fetch error for', kind, err); finishOne(); }); | |||
} | } | ||
// Load | // Load any explicit sources (optional) | ||
['regions','populations','accessions'].forEach(function(kind) { | ['regions', 'populations', 'accessions'].forEach(function (kind) { | ||
if (el.dataset[kind]) loadData(kind, el.dataset[kind]); | |||
}); | }); | ||
// Load SMW query | // Load SMW query as accessions | ||
if (el.dataset.smwQuery) { | if (el.dataset.smwQuery) { | ||
fetchSMW(el.dataset.smwQuery, function(err, geojson) { | fetchSMW(el.dataset.smwQuery, function (err, geojson) { | ||
if (err) { | if (err) { finishOne(); return; } | ||
addGeoJSON('accessions', geojson); | |||
}); | }); | ||
} | } | ||
if ( | if (expected === 0) { | ||
// No data configured | |||
map.setView([15, 105], 5); | map.setView([15, 105], 5); | ||
setTimeout(function(){ | setTimeout(function () { map.invalidateSize(true); }, 100); | ||
} | } | ||
// Tiny zoom readout | |||
var zoomDisplay = L.control({ position: 'topright' }); | var zoomDisplay = L.control({ position: 'topright' }); | ||
zoomDisplay.onAdd = function() { | zoomDisplay.onAdd = function () { | ||
var div = L.DomUtil.create('div'); | var div = L.DomUtil.create('div'); | ||
div.style.cssText = | div.style.cssText = | ||
'background: rgba(255,255,255,0.9);' + | 'background: rgba(255,255,255,0.9);' + | ||
'padding: 5px 8px;' + | 'padding: 5px 8px;' + | ||
| Line 364: | Line 334: | ||
'color: #666;' + | 'color: #666;' + | ||
'border: 1px solid #ccc;'; | 'border: 1px solid #ccc;'; | ||
div. | div.textContent = 'Zoom: ' + map.getZoom(); | ||
map.on('zoomend', function () { div.textContent = 'Zoom: ' + map.getZoom(); }); | |||
map.on('zoomend', function() { | |||
return div; | return div; | ||
}; | }; | ||
| Line 381: | Line 347: | ||
addCSS(CDN + 'leaflet.css', 'leaflet-css'); | addCSS(CDN + 'leaflet.css', 'leaflet-css'); | ||
addJS(CDN + 'leaflet.js', function () { | addJS(CDN + 'leaflet.js', function () { | ||
console.log('Leaflet loaded | console.log('[lw-map] Leaflet loaded; initializing maps'); | ||
init(); | init(); | ||
if (window.mw && mw.hook) { | if (window.mw && mw.hook) { | ||
mw.hook('wikipage.content').add(function ($c) { | mw.hook('wikipage.content').add(function ($c) { | ||
init($c && $c[0] ? $c[0] : document); | init($c && $c[0] ? $c[0] : document); | ||
}); | }); | ||
} | } | ||
}); | }); | ||
})(); | })(); | ||
Revision as of 09:38, 11 January 2026
/* Landrace.wiki – Leaflet map with SMW Ask API support (Conservation Priority) */
(function () {
var LVER = '1.9.4';
var CDN = 'https://unpkg.com/leaflet@' + LVER + '/dist/';
function addCSS(href, id) {
if (id && document.getElementById(id)) return;
var l = document.createElement('link');
l.rel = 'stylesheet';
l.href = href;
if (id) l.id = id;
document.head.appendChild(l);
}
function addJS(src, cb) {
if (window.L && typeof window.L.map === 'function') return cb();
var existing = document.querySelector('script[data-lw-leaflet="1"]');
if (existing) {
existing.addEventListener('load', cb);
return;
}
var s = document.createElement('script');
s.src = src;
s.setAttribute('data-lw-leaflet', '1');
s.onload = cb;
s.onerror = function () { console.error('Leaflet failed to load:', src); };
document.head.appendChild(s);
}
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
return ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]);
});
}
// Conservation Priority colors: Critical / High / Medium / Low
function statusColor(s) {
return ({
'Critical': '#e74c3c',
'High': '#e67e22',
'Medium': '#f1c40f',
'Low': '#2ecc71'
})[s] || '#3498db';
}
function popupForAccession(p) {
var title = [p.id, p.name].filter(Boolean).join(' — ');
var status = p.status ? esc(p.status) : '';
var link = p.page_url ? '<br><a href="' + esc(p.page_url) + '">Open accession</a>' : '';
return '<b>' + esc(title) + '</b>' + (status ? '<br>' + status : '') + link;
}
// Convert SMW Ask API response to GeoJSON FeatureCollection
function smwToGeoJSON(data) {
var features = [];
var results = (data && data.query && data.query.results) ? data.query.results : (data && data.results ? data.results : null);
if (!results) {
console.log('[lw-map] No results in SMW response');
return { type: 'FeatureCollection', features: [] };
}
for (var pageName in results) {
if (!Object.prototype.hasOwnProperty.call(results, pageName)) continue;
var item = results[pageName] || {};
var printouts = item.printouts || {};
var coordsArr = printouts['Has GPS coordinates'];
if (!coordsArr || !coordsArr.length) continue;
var coord = coordsArr[0];
var lat = null, lon = null;
// SMW coords often come back as an object with lat/lon
if (coord && typeof coord === 'object') {
lat = coord.lat;
lon = coord.lon != null ? coord.lon : (coord.lng != null ? coord.lng : coord.long);
} else if (typeof coord === 'string') {
// Try "lat, lon"
var parts = coord.split(',');
if (parts.length === 2) {
lat = parseFloat(parts[0].trim());
lon = parseFloat(parts[1].trim());
}
}
if (lat == null || lon == null || isNaN(lat) || isNaN(lon)) {
console.log('[lw-map] Invalid coordinates for', pageName, coord);
continue;
}
// Name
var nameArr = printouts['Has descriptive name'];
var name = (nameArr && nameArr.length) ? nameArr[0] : pageName;
// Conservation Priority
var statusArr = printouts['Has conservation priority'];
var status = (statusArr && statusArr.length) ? statusArr[0] : '';
// Accession ID
var idArr = printouts['Has accession ID'];
var idVal = (idArr && idArr.length) ? idArr[0] : '';
if (idVal && typeof idVal === 'object' && idVal.fulltext) idVal = idVal.fulltext;
// Page URL (prefer SMW fullurl, otherwise MW util)
var pageUrl = item.fullurl || (window.mw && mw.util && mw.util.getUrl ? mw.util.getUrl(pageName) : ('/wiki/' + encodeURIComponent(pageName)));
features.push({
type: 'Feature',
properties: {
id: idVal || '',
name: name || pageName,
status: status || '',
page_url: pageUrl
},
geometry: {
type: 'Point',
coordinates: [lon, lat]
}
});
}
console.log('[lw-map] Converted', features.length, 'features from SMW');
return { type: 'FeatureCollection', features: features };
}
function fetchSMW(askQuery, callback) {
if (!window.mw || !mw.config) {
callback(new Error('MediaWiki config not available'), null);
return;
}
var apiUrl = mw.config.get('wgScriptPath') + '/api.php';
var params = new URLSearchParams({
action: 'ask',
query: askQuery,
format: 'json'
});
console.log('[lw-map] Fetching SMW query:', askQuery);
fetch(apiUrl + '?' + params.toString())
.then(function (r) {
if (!r.ok) throw new Error('SMW API error: ' + r.status);
return r.json();
})
.then(function (data) {
var geojson = smwToGeoJSON(data);
callback(null, geojson);
})
.catch(function (err) {
console.error('[lw-map] SMW fetch error:', err);
callback(err, null);
});
}
function buildLegendControl() {
var legend = L.control({ position: 'bottomleft' });
legend.onAdd = function () {
var div = L.DomUtil.create('div', 'lw-legend-map');
div.style.cssText =
'background: white;' +
'padding: 12px;' +
'border-radius: 8px;' +
'box-shadow: 0 2px 10px rgba(0,0,0,0.2);' +
'border: 1px solid #ccc;' +
'font-size: 12px;' +
'line-height: 1.4;';
var html = '<div style="font-weight:700;margin-bottom:8px;color:#333;">Conservation Priority</div>';
var items = [
['Critical', '#e74c3c'],
['High', '#e67e22'],
['Medium', '#f1c40f'],
['Low', '#2ecc71']
];
items.forEach(function (it) {
html +=
'<div style="display:flex;align-items:center;margin:6px 0;">' +
'<div style="width:12px;height:12px;background:' + it[1] + ';border-radius:50%;margin-right:8px;border:1px solid rgba(0,0,0,0.2);"></div>' +
'<span style="color:#555;">' + esc(it[0]) + '</span>' +
'</div>';
});
div.innerHTML = html;
return div;
};
return legend;
}
function initOne(el) {
if (el.dataset.init) return;
el.dataset.init = '1';
if (el.clientHeight < 100) el.style.height = '70vh';
var map = L.map(el, { minZoom: 2, maxZoom: 15, zoomControl: true });
map.setView([15, 105], 5);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OSM' }).addTo(map);
buildLegendControl().addTo(map);
var layers = [];
var done = 0;
var expected = 0;
// External/inline sources (optional)
['regions', 'populations', 'accessions'].forEach(function (k) {
if (el.dataset[k]) expected++;
});
// SMW query source (optional)
if (el.dataset.smwQuery) expected++;
function finishOne() {
done++;
if (done >= expected) fitToDataOrDefault();
}
function fitToDataOrDefault() {
try {
if (layers.length) {
var group = L.featureGroup(layers);
var b = group.getBounds();
if (b && b.isValid()) {
map.fitBounds(b.pad(0.2), { maxZoom: 10 });
} else {
map.setView([15, 105], 5);
}
} else {
map.setView([15, 105], 5);
}
} catch (e) {
console.error('[lw-map] fit error:', e);
map.setView([15, 105], 5);
}
setTimeout(function () { map.invalidateSize(true); }, 100);
}
function addGeoJSON(kind, g) {
if (!g || !g.features || !g.features.length) {
console.log('[lw-map] No features for', kind);
finishOne();
return;
}
var layer = L.geoJSON(g, {
style: function (f) {
return {
color: statusColor((f.properties || {}).status),
weight: 2,
fillOpacity: 0.2,
opacity: 0.8
};
},
pointToLayer: function (f, ll) {
return L.circleMarker(ll, {
radius: 5,
color: '#fff',
weight: 2,
fillColor: statusColor((f.properties || {}).status),
fillOpacity: 0.9
});
},
onEachFeature: function (f, ly) {
var p = (f && f.properties) ? f.properties : {};
ly.bindPopup(popupForAccession(p));
var tip = p.id || p.name || '';
if (tip) ly.bindTooltip(esc(tip), { direction: 'top', offset: [0, -8], opacity: 0.9 });
}
}).addTo(map);
layers.push(layer);
finishOne();
}
function loadData(kind, data) {
var trimmed = (data || '').trim();
if (!trimmed) { finishOne(); return; }
// Inline JSON
if (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {
try {
var parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) parsed = { type: 'FeatureCollection', features: parsed };
addGeoJSON(kind, parsed);
} catch (e) {
console.error('[lw-map] JSON parse error for', kind, e);
finishOne();
}
return;
}
// URL fetch
fetch(trimmed)
.then(function (r) { if (!r.ok) throw new Error(kind + ' fetch ' + r.status); return r.json(); })
.then(function (json) { addGeoJSON(kind, json); })
.catch(function (err) { console.error('[lw-map] Fetch error for', kind, err); finishOne(); });
}
// Load any explicit sources (optional)
['regions', 'populations', 'accessions'].forEach(function (kind) {
if (el.dataset[kind]) loadData(kind, el.dataset[kind]);
});
// Load SMW query as accessions
if (el.dataset.smwQuery) {
fetchSMW(el.dataset.smwQuery, function (err, geojson) {
if (err) { finishOne(); return; }
addGeoJSON('accessions', geojson);
});
}
if (expected === 0) {
// No data configured
map.setView([15, 105], 5);
setTimeout(function () { map.invalidateSize(true); }, 100);
}
// Tiny zoom readout
var zoomDisplay = L.control({ position: 'topright' });
zoomDisplay.onAdd = function () {
var div = L.DomUtil.create('div');
div.style.cssText =
'background: rgba(255,255,255,0.9);' +
'padding: 5px 8px;' +
'border-radius: 4px;' +
'font-size: 11px;' +
'color: #666;' +
'border: 1px solid #ccc;';
div.textContent = 'Zoom: ' + map.getZoom();
map.on('zoomend', function () { div.textContent = 'Zoom: ' + map.getZoom(); });
return div;
};
zoomDisplay.addTo(map);
}
function init(root) {
(root || document).querySelectorAll('.lw-map').forEach(initOne);
}
addCSS(CDN + 'leaflet.css', 'leaflet-css');
addJS(CDN + 'leaflet.js', function () {
console.log('[lw-map] Leaflet loaded; initializing maps');
init();
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function ($c) {
init($c && $c[0] ? $c[0] : document);
});
}
});
})();