在Obsidian中,通过Dataview插件可以创建实用的摄影辅助工具,直接在笔记中动态显示天气数据和最佳拍摄时间。以下是实现方法:
城市天气实时显示
- 调用wttr.in天气API获取数据
- 自动转换英文天气描述为中文+Emoji
- 显示温度范围、风速、湿度等关键信息
const container = dv.container;
const nameKey = "my_location_city";
let city = localStorage.getItem(nameKey) || "shanghai";
// 读取所有 --geo- 开头的自定义属性
function getAllCityKeys() {
const keys = [];
for (let sheet of document.styleSheets) {
let rules;
try { rules = sheet.cssRules; }
catch { continue; }
if (!rules) continue;
for (let rule of rules) {
if (rule.selectorText === ":root") {
for (let i = 0; i < rule.style.length; i++) {
const prop = rule.style[i];
if (prop.startsWith("--geo-")) {
keys.push(prop.replace("--geo-", "").trim());
}
}
}
}
}
return keys;
}
// 根据拼音获取城市信息
function getCityInfo(pinyin) {
const varName = `--geo-${pinyin}`;
const raw = getComputedStyle(document.documentElement)
.getPropertyValue(varName)
.trim()
.replace(/["']/g, "");
if (!raw) return null;
const [lat, lon, name] = raw.split(",");
return { lat: +lat, lon: +lon, name: name.trim() };
}
// 天气图标映射
function weatherIcon(desc) {
const d = desc.toLowerCase();
if (d.includes("thunder")) return "⛈️ 雷阵雨";
if (d.includes("snow")) return "❄️ 下雪";
if (d.includes("mist")||d.includes("fog")||d.includes("haze")) return "🌫️ 雾霾";
if (d.includes("rain")) return "🌧️ 雨";
if (d.includes("cloud")||d.includes("overcast")||d.includes("partly")) return "☁️ 多云";
if (d.includes("sun")||d.includes("clear")) return "☀️ 晴";
return "🌈 其他";
}
// 渲染主内容
const info = getCityInfo(city);
if (!info) {
container.innerHTML = `<span>⚠️ 无法识别城市 ${city}</span>`;
} else {
const { name } = info;
const today = new Date().toLocaleDateString();
container.innerHTML = `
<div class="loc-sun-header" id="locHeader">
<span>📅 ${today}</span>
<span id="toggleFormBtn" class="loc-sun-btn" tabindex="0">📍${name}</span>
<span id="weatherInfo">☁️ 加载天气中...</span>
</div>
<div id="geoForm" style="display:none; margin-top:8px;">
<label style="display:flex; align-items:center; gap:6px;">
城市拼音:
<input type="text" id="locCity" list="cityAuto"
placeholder="如 beijing" style="width:160px;" />
<button id="saveBtn">保存</button>
</label>
<datalist id="cityAuto"></datalist>
</div>
`;
// 获取所有城市键和对应信息
const keys = getAllCityKeys();
const cityInfos = keys.map(k => ({ key: k, name: getCityInfo(k)?.name || "" }));
// 填充 datalist
const datalist = container.querySelector("#cityAuto");
cityInfos.forEach(ci => {
const opt = document.createElement("option");
opt.value = ci.key;
opt.label = `${ci.key} - ${ci.name}`;
datalist.appendChild(opt);
});
// 获取并显示天气
fetch(`https://wttr.in/${encodeURIComponent(name)}?format=j1`)
.then(res => res.json())
.then(data => {
const w = data.weather[0].hourly[4];
const icon = weatherIcon(w.weatherDesc[0].value);
const wind = w.windspeedKmph;
const dir = w.winddir16Point;
const humidity = w.humidity;
const minT = data.weather[0].mintempC;
const maxT = data.weather[0].maxtempC;
const windMap = {
N:"⬆️正北",NE:"↗️东北",E:"➡️正东",SE:"↘️东南",
S:"⬇️正南",SW:"↙️西南",W:"⬅️正西",NW:"↖️西北"
};
const windText = windMap[dir]||dir;
container.querySelector("#weatherInfo").innerText =
`${icon} ${minT}°C~${maxT}°C 🍃${wind}km/h ${windText} 💧${humidity}%`;
})
.catch(() => {
container.querySelector("#weatherInfo").innerText = `❌ 天气获取失败`;
});
// 设置交互逻辑
setTimeout(() => {
const toggleBtn = container.querySelector("#toggleFormBtn");
const form = container.querySelector("#geoForm");
const cityInput = container.querySelector("#locCity");
const saveBtn = container.querySelector("#saveBtn");
// 点击城市名称,切换表单显示并聚焦
toggleBtn.onclick = () => {
form.style.display = form.style.display === "none" ? "block" : "none";
if (form.style.display === "block") cityInput.focus();
};
// 输入联想过滤
cityInput.addEventListener("input", () => {
const val = cityInput.value.toLowerCase();
datalist.innerHTML = "";
cityInfos
.filter(ci => ci.key.includes(val))
.forEach(ci => {
const opt = document.createElement("option");
opt.value = ci.key;
opt.label = `${ci.key} - ${ci.name}`;
datalist.appendChild(opt);
});
});
// 保存逻辑
saveBtn.onclick = () => {
const inp = cityInput.value.trim().toLowerCase().replace(/[^\w]/g, "");
if (!keys.includes(inp)) {
alert("❌ 未识别城市拼音,请输入如 beijing、shanghai 等");
cityInput.focus();
return;
}
localStorage.setItem(nameKey, inp);
location.reload();
};
cityInput.addEventListener("keypress", e => {
if (e.key === "Enter") saveBtn.click();
});
}, 0);
}
相关摄影时刻&日出日落事件显示
- 基于经纬度的天文算法
- 自动计算黄金时刻和蓝色时刻
- 直观的时间段可视化展示
const container = dv.container;
const nameKey = "my_location_city";
let city = localStorage.getItem(nameKey) || "shenzhen";
function getCityInfo(pinyin) {
const raw = getComputedStyle(document.documentElement).getPropertyValue(`--geo-${pinyin}`).trim();
if (!raw) return null;
const [lat, lon, name] = raw.replace(/"/g, "").split(",");
return { lat: parseFloat(lat), lon: parseFloat(lon), name };
}
function getSunTimes(date, lat, lon) {
const rad = Math.PI / 180;
const day = Math.floor((Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) - Date.UTC(date.getFullYear(), 0, 0)) / 86400000);
const lngHour = lon / 15;
const t_rise = day + ((6 - lngHour) / 24);
const t_set = day + ((18 - lngHour) / 24);
function calcTime(t) {
const M = (0.9856 * t - 3.289) % 360;
let L = M + (1.916 * Math.sin(M * rad)) + (0.020 * Math.sin(2 * M * rad)) + 282.634;
L = (L + 360) % 360;
let RA = Math.atan(0.91764 * Math.tan(L * rad)) / rad;
RA = (RA + 360) % 360;
RA += Math.floor(L / 90) * 90 - Math.floor(RA / 90) * 90;
RA /= 15;
const sinDec = 0.39782 * Math.sin(L * rad);
const cosDec = Math.cos(Math.asin(sinDec));
const cosH = (Math.cos(90.833 * rad) - sinDec * Math.sin(lat * rad)) / (cosDec * Math.cos(lat * rad));
if (cosH > 1 || cosH < -1) return null;
const H = (t === t_rise ? 360 - Math.acos(cosH) / rad : Math.acos(cosH) / rad) / 15;
const T = H + RA - 0.06571 * t - 6.622;
const UT = (T - lngHour + 24) % 24;
return UT;
}
function toLocal(ut) {
const d = new Date(date);
d.setUTCHours(0, 0, 0, 0);
d.setUTCMinutes(ut * 60);
return d;
}
const riseUT = calcTime(t_rise);
const setUT = calcTime(t_set);
return {
sunrise: toLocal(riseUT),
sunset: toLocal(setUT),
};
}
function fmt(d) {
return d.getHours().toString().padStart(2, "0") + ":" + d.getMinutes().toString().padStart(2, "0");
}
const info = getCityInfo(city);
if (!info) {
container.innerHTML = `<span>⚠️ 无法识别城市 ${city}</span>`;
} else {
const { lat, lon } = info;
const today = new Date();
const times = getSunTimes(today, lat, lon);
if (!times.sunrise || !times.sunset) {
container.innerHTML = `<span>⚠️ 当前城市无法计算日出/日落</span>`;
} else {
const sr = times.sunrise, ss = times.sunset;
const make = ms => new Date(ms);
const blueMorningStart = make(sr - 45 * 60000), blueMorningEnd = make(sr - 15 * 60000);
const goldenMorningStart = make(sr - 15 * 60000), goldenMorningEnd = make(sr + 30 * 60000);
const goldenEveningStart = make(ss - 60 * 60000), goldenEveningEnd = make(ss + 15 * 60000);
const blueEveningStart = make(ss + 15 * 60000), blueEveningEnd = make(ss + 45 * 60000);
container.innerHTML = `
<div class="loc-sun-line">
<span>🟦 ${fmt(blueMorningStart)} - ${fmt(blueMorningEnd)}</span>
<span>🟨 ${fmt(goldenMorningStart)} - ${fmt(goldenMorningEnd)}</span>
<span>🌅 ${fmt(sr)}</span>
<span>🌇 ${fmt(ss)}</span>
<span>🟨 ${fmt(goldenEveningStart)} - ${fmt(goldenEveningEnd)}</span>
<span>🟦 ${fmt(blueEveningStart)} - ${fmt(blueEveningEnd)}</span>
</div>
`;
}
}
自定义 CSS 样式
下面是两个css文件,包含了显示样式和亿些城市的经纬度
文件资源管理器中导航至Vault\.obsidian\snippets
,将css文件放在里面
然后在Obsidian中设置-外观-自定义CSS代码
启用相关css代码