浮云游子意 Floating Cloud on Journey

Leonson's personal blog

辩裁无须七年期

现在时间是2026年1月10日下午2:14,我坐在某高中的食堂。今天这座高中举办一场辩论邀请赛,食堂被临时开辟为裁判等候区,我在等候下一张ballot(选票)出现在我的邮箱里。之前的几个小时里,我的孩子比赛了两轮,我也做了两轮裁判。我简单写写我在美国学校做辩论裁判的经历,我希望更多的华人家长来做辩论裁判,这对我们的孩子在辩论这个圈子的未来,在我看来,会有帮助。 一年前差不多的时候,同样的高中也举办了同样的辩论邀请赛,我的孩子也参加了。不过,当时的我完全不想来做辩论赛的裁判:首先我自己就不擅长辩论,包括母语,何况英语?其次,我也不懂孩子们这些辩论赛的规矩和门道。这边的辩论赛形式还挺多样,不一一列举了,不过对于初中的辩论初学者来说,他们就专做一种叫做“Parli”的辩论形式。这种形式是模仿英国的议会辩论,“Parli”这个词也是英国议会(British Parliament)这个词的简写。每个参赛队伍由两人组成,按学校组织。每一轮辩论会提前宣布辩题,每个辩题相当于一个“政府提案”,例如“美国应当加强在北极的军事存在”,正方的职责是说服裁判:议会应当通过这个提案;而反方的职责是让裁判相信“政府提案应当被否决”。正反双方有二十分钟准备时间,比赛开始后依次发言:正方一辩7分钟立论,反方一辩8分钟反驳和立论,正反方二辩各自8分钟发言扩展,之后反方一辩4分钟总结,最后正方一辩5分钟总结。结束后大家散伙,裁判在系统内提交“ballot”——相当于议员投票,不过这个议会只有一个议员。 差不多那个时间,我家的家庭议会也有了一个提案,“Leonson应当去做Parli比赛的裁判”,在辩论回合中,正方使用了关键性的证据:我家小孩有一次参加辩论,裁判居然是对方一名辩手的家长1,辩论的结果,虽然我们娃觉得自己表现更好,但是裁判仍然判对手获胜,给的理由据说也不让人信服。反方在辩论中也重复了“不擅长”“不懂”这些论点,但是重复的论调在辩论中不是什么好的战术,无法说服议会。最终,正方获胜。 三月份,根据家庭议会决定,我第一次去东湾一所高中做了一整天的裁判。下半年,第二次;之后,又有第三次。今天这是第四次了。早上刚到裁判休息区安顿下来,椅子还没有坐热,系统就提示我的ballot来了——我需要马上点一个按钮“我收到了ballot”,然后到相应的教室去。在那间教室安顿下来后我发现,我之前裁决的都是novice轮(新手村),这一轮是varsity(高手组),不能不说我还有点紧张,就像去年三月第一次裁决新手村比赛一样紧张。不过,这一轮过去,紧接着下一轮varsity我又裁决一轮,我发现,原来做varsity裁判也不难,成为一个辩论赛裁判,不需要三日试玉七年辨材这么漫长,边做边学也可以从连滚带爬做到游刃有余。后来我去问了组织方:我本来是来做novice裁判的,怎么让我去做varsity了?组织方说:今天新手裁判有点多,他们还在新手村,高手那边缺裁判,你当然就要去了。 下面简单记录一下早上第一轮比赛的过程,有心做裁判的家长可以看看。 辩论题目是The United States federal government should significantly increase its anti-piracy efforts in Djibouti. 翻译成中文:美国联邦政府应当显著加强在吉布提的反海盗努力。 正方一辩提出花1.5个亿做这件事,原因是虽然海盗活动曾经下降90%但近两年又有回潮,论点包括经济影响(海盗影响运输成本),人道主义影响(海盗威胁人身安全),以及战略稳定。 反方一辩驳斥:靠军事压力不解决根本问题,如果不解决索马里的贫困问题,海盗会源源不断出现,因为他们没有出路。1.5亿也不是很多钱。我们提出花更多的钱来解决根本。论点包括现行方法已经很有效,机会成本,以及冲突恶化的风险(激怒胡塞武装2)。 正方二辩:你们要花更多钱解决贫困问题,你们看看美国在阿富汗花了那么多钱扶贫,塔利班就放下武器了吗?问题解决了吗? 反方二辩:阿富汗是军事入侵的结果,美国不是去扶贫的,索马里不一样,我们不是入侵索马里,是帮助。看看中国的一带一路,这才是从根本消除贫困和海盗的土壤3。 之后双方的总结,按照规则,都必须只能引用之前提到过的观点和证据,不能有新的观点和证据,否则是犯规的。所以就不记录了。 最后想说的是,如果读者读完之后有兴趣,欢迎提问,可以通过评论区或者其他你知道的方式联系我。我之后会逐渐收集对我有过帮助的资料更新到这里,特别去年三月在东湾我第一次裁判时,主办方提供的裁判训练资料就很有帮助。这个辩论赛是对新手辩手和新手裁判双料友好的,每年三月会举行,如果想来试试,我非常推荐。 (function() { var mapElementId = 'post-map-map-1768088466533'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'el_cerrito_hs' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { 'el_cerrito_hs': "El Cerrito高中,每年三月举办辩论赛" }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 第一次做裁判的地方 这个其实是明显的conflict of interests(利益冲突),是严格禁止的,可惜孩子当时不知道。 ↩ 这儿还有个小插曲,正方(利用POI规则)说我们说吉布提和索马里海盗你提胡塞干啥呢;反方说“胡塞就在吉布提家门口,就五十英里开外,字面意义的家门口”。 ↩ 这是我第一次在美国的中学辩论里听到有辩手说中国的好话,并且没有辩手说中国的坏话的。 ↩

2026/1/10
articleCard.readMore

索友

很久很久以前1,一天,我在一本《经济学人》上读到一则小文,标题不太确定了,要么是“台湾与索马里兰”,要么是“索马里兰与台湾”2。当时我对索马里兰这个名字只有点模糊印象3,就决定读一下。文章是这样开头的:”One is a small, surprisingly successful and relatively democratic country bullied by a larger, dictatorial neighbour which considers it to be part of its own territory.” 当时我冷笑着,内心:我当然知道你说的是谁了。翻译过来基本是:“一个小国,但是惊人的成功,相对来说民主。它总被一个大的,独裁的邻居欺负,这个邻居认为这个小国是他们的领土的一部分。”这不明摆着说台湾嘛,看来大英帝国的《经济学人》又来辱华了。我继续往下读:“The other is Taiwan.” 我:“什么?你第一句话说的不是台湾吗?”那一瞬间我对《经济学人》由衷地佩服:还是老牌帝国主义的媒体会玩,一点儿辱华的把柄都没有,读者脑子里想的是什么,与他们留下的文字无关。并且《经济学人》这样的描写还挺合理的:那个“larger, dictatorial“并且”considers it to be part of its own territory“的邻居,其实就是首都位于摩加迪沙4的索马里联邦政府。以联邦政府的观点来看,索马里兰原本属于索马里,1991年自行宣布独立,这一行为是非法而无效的,所以”索马里兰是索马里领土不可分割的一部分“。 不过索马里兰当然有他们自己的正当理由:在殖民时期,如今索马里兰的土地是大英帝国殖民的,而如今索马里的其他地区的土地是意大利殖民的。前者叫“英属索马里兰”,后者叫“意属索马里兰”。前者在1960年6月27日就宣布独立建国了,只不过过了几天,出于当时“一个索马里兰”的美好愿望,于7月1日和后独立的原“意属索马里兰”合并,成立了“索马里共和国”。但是,合并后原来的英属索马里兰地区,以及当地的主体氏族5伊萨克族被边缘化,后来有一个名叫西亚德·巴雷的将领又政变上台,实行独裁统治,更是让原英属索马里兰人民后悔不迭。后来当地人发动起义,西亚德·巴雷出兵镇压,在1987年到1989年间甚至无差别轰炸城市和平民,造成这一地区的中心城市哈尔格萨90%被摧毁6,成千上万人死亡7。所以按照索马里兰的说法,当1991年他们和其他各路索马里反对派一起推翻西亚德·巴雷独裁统治之后,宣布独立,合情合理。 但是,索马里兰的说法并没有获得国际社会的普遍认可,即便他们“surprisingly successful and relatively democratic”,一直到2020年,同样不被国际社会普遍承认的台湾,和索马里兰建立了外交关系。《经济学人》那篇文章就是报导这一新闻的。文章最后这样说:“斯威士兰原本是台湾在非洲的最后一个朋友,从那时候起台湾有两个朋友了。”索马里兰呢?只有台湾一个朋友。 五年过去,事情有了转变,索马里兰获得了第二个朋友:以色列。这也是第一个承认索马里兰的联合国正式成员国。据说,哈尔格萨全城欢庆。按说索马里兰该举国欢庆了吧? 这事还真不好说。因为索马里兰虽然五年间朋友翻了一番,可是国土面积却变少了。索马里兰主张的领土范围,是和当年英属索马里兰的领土范围重合的。不过这片土地上不仅仅生活着之前受欺负的伊萨克族,也生活着其他氏族,例如东部的Dhulbahante(杜尔巴汉特)族。杜尔巴汉特族主张在自治的前提下保留在索马里联邦,于是和哈尔格萨的索马里兰政府龃龉不断,在这过程中,杜尔巴汉特族的精英人士不断遭到暗杀。2022年末,在索马里兰东部小城Las Anod,又有一位精英人士在走出清真寺时被身份至今不明的枪手枪杀。当地人民怒了,认定这次刺杀是索马里兰干的,上街抗议了。索马里兰军队开枪镇压,随即于2023年1月撤走并包围了城市,当地氏族于二月成立了政权,名为SSC-Khatumo8,并建立了武装反抗。从2月开始的6个月中,索马里兰军队对小城进行了无差别炮击,包括医院在内,造成平民死伤和流离失所,还断了小城的水源,不过4月的降雨让断水战术失效了。8月底,Khatumo方面出人意料地突袭了城外索马里兰军队的据点(可以比作太平军击破江北大营江南大营了)。索马里兰军队被驱退100公里(当然索马里兰方面表述为“进行了战略转移”),索马里兰的版图也缩水了大约四分之一。虽然索马里兰总统坚持“三区是索马里兰领土不可分割的一部分”,但就像索马里兰从索马里事实独立一样,Khatumo地方也从索马里兰事实独立了。 最后,作为一个《经济学人》粉,还是要看看偶像对这事怎么看。果然偶像开篇又不一般:“When the Israeli flag is sighted on the streets of the Muslim world, it is often being set alight or trampled underfoot. ”中译:当以色列国旗在穆斯林世界的街道上出现时,一般是被点上火烧的,或者是放地上踩的。“Yet in recent days the Star of David has been plastered on buildings and brandished by jubilant crowds in Hargeisa, the capital of Somaliland.”中译:这几天大卫星旗在哈尔格萨的建筑上飘扬着,欢乐的人们挥舞着。虽说文中没提两年前索马里兰丢失土地那回事,不过五年后这篇的索马里兰地图中间多了一道虚线。所以索马里兰这五年是处境更好了还是更不好了,我看不清,你说呢? (function() { var mapElementId = 'post-map-map-1767555925853'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'hargeysa', 'las_anod', 'mogadishu' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { 'hargeysa': "哈尔格萨,索马里兰首都", 'las_anod': "Las Anod,三区首都", 'mogadishu': "摩加迪沙,索马里首都" }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 北方双城分庭抗礼,南方隔岸观火 参考资料 英文维基百科:索马里兰。 英文维基百科:针对伊萨克人的种族灭绝。 英文维基百科:Las Anod冲突。 经济学人:索马里兰2024年独立日深度报导。 大概是2020年10月。 ↩ 纸版《经济学人》的标题和现在找到的网页版标题是不一样的,现在找不到当时的纸版书了。 ↩ 小时候读过一本按各大洲讲国际时事的书,里面有一篇专讲索马里。现在这本书也找不到了。 ↩ 郑和下西洋的时候,叫做木骨都束,显而易见是一回事。 ↩ 都是父系氏族,以血缘传承。 ↩ 哈尔格萨当时被形容为非洲的德累斯顿。 ↩ 根据估计,在1987年到1989年间,有五万到十万伊萨克人平民遇难。统计范围是整个伊萨克人生活的区域,不仅仅是哈尔格萨一地。 ↩ SSC表示Sool, Sanaag和Cayn三个区,Khatumo的意思是“最终决定”,可以说是三区革命了。 ↩

2026/1/4
articleCard.readMore

冒个泡

自从年初发了一篇博客,不知不觉,已经要年尾了博客还荒废着,不得不冒泡报平安了。怒水一篇,不上十大,经验值不要,版主勿删。 博客荒废的原因也很简单直接,生成式AI,不必解释你我都懂。博主也曾严肃地花了五分钟时间考虑要不要转型改成AI辅助写作:自己写大纲,然后交给ChatGPT来填充文章细节,只要让ChatGPT把博主的博文提前读一遍就足够了,反正博主这个小站文字不多消耗token也不会多。 不过,博主自己就不喜欢读AI生成的文字,估计乐意读这个博客的朋友(小众中的小众)也多多少少想法类似。要让博主从哼哧哼哧写自娱自乐的玩意,转变成写提示词让AI帮忙生产些自己都不想去读的东西,无异于让博主自抡耳光。况且博主早早加了个”Written by human not by AI”声明(页面底部就可以看到),有这么浓眉大眼的图标在那儿摆着,博主怎么能随便叛变革命呢?况且,“Not By AI”现在做大了:对于任何想添加这个声明的网站,要么一次性交99美金,要么提交证据让他们人工审核一番,然后,下载一个图标。博主当年是直接下载就行了,当时一时冲动给自己立的“不用AI写blog”的flag,居然在N年后帮忙省了99,怎么也得接着自己写,才对得起自己嘛。 其实Not By AI并不是想钱想疯了,只要博主真的是人工创作,审核过就好了。博主觉得并没有多少人会为了省几天的人工审核时间来交这99美金 不过,这年头博客当然也不可能完全没有AI的帮助。就拿咱们这小站来说吧,之前博主已经找AI写了些代码,加了些新功能,比如在博文里加上地图,并且有个专门的页面,可以在地图上点选标出来的地方,就可以进入相应的页面。如下图所示: 这个页面有个地图,再点地图上的小蓝标会显示地点,并且带有链接。点击链接就会进入对应的文字,例如《埠出厦门行》这篇。 当时功能做好后博主受此刺激,又输出了几篇,都带着地图,心情很欢乐。但是博主每次都得自己做一堆操作: 一边写,一边想:加哪些地点好呢? 文字写完了,保存文件。 打开一个小工具(Claude或者ChatGPT帮忙写的,简单的HTML),在地图上找到想加入的点,点一下,就copy好了这个点的经纬度。 打开这个博客文件夹下面的一个YML文件,把刚才copy下来的东西粘贴进去,再加一个id,并且加一些说明性质的文字。 切换到第二步的文件,把第四步里加入的id,放到文件前面的“front matter”里边去。 如果在第一步里想到了多个地点,那么重复第三到第五步。 最后,在第二步的文件中,决定在那里显示地图,将一小段代码插入到相应的位置。 博主的热情,在经历了上一篇之后,自然而然地消散了。 最近博主和老板聊天(博主工作不常换,组也不常换,但是老板经常换,上次博客里提到的老板,已经是前前前前前前前老板了,在博主的前组),聊到AI Native,老板说,不要假设AI不能做什么,要破除迷信,破除固定思维,博主说懂了,就像Think Costco First,我们是Think AI First。于是一天晚上,博主打开Cursor,问了Cursor下面一段话: 这一长段提示词无非是提需求,让Cursor帮忙出个方案。 Cursor想了想,提了三个方案: 方案一:写个Python script,和AI集成。 方案二:写个Cursor或者VS Code的提示词模板。 方案三:写个VS Code Task并且和AI集成。 三个方案Cursor都哐哐哐把框架代码写出来了。Cursor还给了个推荐,选方案一,只需要做下面这些事就可以了: Cursor的最佳方案,需要很多命令行交互,并且需要使用API和LLM提供商例如OpenAI集成。 博主说,我都已经把Cursor当编辑器了,还要这么多花头干什么,做方案二吧。 几个来回Cursor就把这事儿完美做成了。博主新打开了一个聊天窗口,让Cursor给《一个老兵》这一篇文字找三个兴趣点并且加上地图标注,瞬间完成。读者诸君,如果你坚持读到这里还没关闭页面的话,请向二月徒手加了十二个地点的本博博主致敬,或者嘲讽,一秒钟。 所以,杂货累活交给AI来干(除了地图标注,还有图片上传,添加内链之类),写东西这种简单的小活,还是坚持古法,自己做一做,不麻烦AI了。 最后强行加个地图,用了之前步骤三里的简单HTML工具。 (function() { var mapElementId = 'post-map-pulse_mpk'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'mpk_creek' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 本文部分构思是今天散步到这里时生成的。之前博主也有关于这里的构思,但是并没有写,惭愧惭愧!

2025/11/8
articleCard.readMore

地趣续

坐在陪审团召集室里翻阅自己以前写的博客,看到十七年半以前这篇《地趣》,决定搞个大新闻,把当时的自己批判一番了:当时怎么那么狂妄地出题目考读者,好像自己会做似的,真是有趣。 欧盟最西的边界线,在什么地方? 如今我查维基百科的相关条目,所谓的欧盟“最西”需要各种限定条件的:如果在地理意义的“欧洲”范围内找,“最西”点在亚速尔群岛的某个小岛,属于葡萄牙;如果在欧洲大陆找,那么是葡萄牙的罗卡角。但是,当时的我说到“边界线”,那么想必我指的是陆路边界了,那么真相只有一个:当时的我心里想的是法属圭亚那和苏里南的边界。但是如今的我反倒疑惑了,就去查,查下来的结果是:法属圭亚那确实法理上是属于欧盟的,而阿鲁巴、法属波利尼西亚这样的属于“海外领地”而不属于欧盟。所以我当时的答案应当是合理的。不过如今又有一个特例需要检查了:北极有个汉斯岛,加拿大和丹麦争执了很多年,最终在2022年决定将这个岛一分为二,所以这个岛上也有一条陆地边界线了。这个岛的经度还确实比法属圭亚那更靠西,是不是时过境迁答案也要更新了呢?我仔细检查了一下,还问了GenAI,结论是不算:汉斯岛一半属于加拿大,另一半属于丹麦治下的格陵兰,而格陵兰早就退出欧盟了。 (function() { var mapElementId = 'post-map-eu'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'cayenne', 'hans_island', 'monchique_islet' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { 'cabinda': "卡宾达,属于安哥拉,以前是葡属刚果。", 'cayenne': "卡宴,法属圭亚那首府", 'hans_island': "汉斯岛,加拿大的第二个陆上邻国在这里", 'monchique_islet': "Monchique Islet,属于葡萄牙亚速尔群岛,一般认为的欧盟最西端。", 'neum': "尼姆,波黑出海口", 'dubrovnik': "杜布罗夫尼克,克罗地亚飞地", }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 欧盟最西端在哪里 为什么安哥拉本土和卡宾达之间被两个刚果包围,却不属于任何一个刚果? 这句话有语病,如果我不是我自己,那我完全不知道我在说什么。现在我可以帮我自己解释一下了,我的意思是,为什么“卡宾达”这个地方被刚果共和国和刚果民主共和国包围,并且和安哥拉并不接壤,却不属于那两个刚果之一,而是属于安哥拉。 我很确定我当时的理解是一知半解的,或者说是皮毛的。这许多年过去,我越来越不敢说我答的上来这个问题了。不过我可以分享我所知道的:在当时我提到的那本我小时候的地图册里,卡宾达被标注成一个独立的国家,不过后来在我任何一本接触到的世界地图里,卡宾达都只是安哥拉的一个省。原因可能是1975年卡宾达短暂独立过,但是几个月之后就被安哥拉出兵吞并了。而安哥拉出兵也有依据:欧洲殖民时期,那一带有法属刚果(后来成为刚果共和国)、比属刚果(后来成为刚果民主共和国)和葡属刚果(就是卡宾达),安哥拉则是葡萄牙殖民地。原本葡萄牙殖民者将葡属刚果与安哥拉区分对待,但后来又合并起来对待了,所以独立后的安哥拉对卡宾达主张了主权并且成功,当然卡宾达当地总有不同意见,并且也有人使用武力表达不同意见,安哥拉也使用武力对应不同意见就是了。 (function() { var mapElementId = 'post-map-cabinda'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'cabinda', 'point_noire', 'mbanza_congo', 'matadi' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { 'cabinda': "卡宾达,属于安哥拉,以前是葡属刚果。", 'cayenne': "卡宴,法属圭亚那首府", 'hans_island': "汉斯岛,加拿大的第二个陆上邻国在这里", 'monchique_islet': "Monchique Islet,属于葡萄牙亚速尔群岛,一般认为的欧盟最西端。", 'neum': "尼姆,波黑出海口", 'dubrovnik': "杜布罗夫尼克,克罗地亚飞地", }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 卡宾达位置 为什么克罗地亚的领土贪婪地向东南延伸,一条狭长的海岸线冷酷地堵住波黑,使其没有一个出海口? 当时的我对细节的注意并不够,其实波黑是有出海口的,叫做尼姆。波斯尼亚和黑塞哥维那在这里有二十公里的海岸线,这二十公里海岸线两边的延长线都属于克罗地亚。著名的杜布罗夫尼克1位于尼姆的东边,是克罗地亚的飞地:所以波黑出海不方便(港口条件不太好),克罗地亚杜布罗夫尼克的陆路交通也不方便,两国只好协商,克罗地亚提供更好的港口,波黑保证陆路畅通。另外,虽然我是我自己,但是我完全不知道我当时的答案是什么,而我现在查到波黑和克罗地亚如此这般的边界划分,在1699年就协定了:当时拉古萨共和国(首都在杜布罗夫尼克)把尼姆割让给了奥斯曼土耳其。 (function() { var mapElementId = 'post-map-bc'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'dubrovnik', 'neum' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { 'cabinda': "卡宾达,属于安哥拉,以前是葡属刚果。", 'cayenne': "卡宴,法属圭亚那首府", 'hans_island': "汉斯岛,加拿大的第二个陆上邻国在这里", 'monchique_islet': "Monchique Islet,属于葡萄牙亚速尔群岛,一般认为的欧盟最西端。", 'neum': "尼姆,波黑出海口", 'dubrovnik': "杜布罗夫尼克,克罗地亚飞地", }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 尼姆和杜布罗夫尼克 1945年的波兰边界和1939年的相比,相对于发生了整体的平移,波兰的领土在战前战后有何得失,领土面积是增多了,还是减少了? 这道题出的还行,答案是减少了。我之所以知道,是在曾经满大街报刊亭的时代,从一本买来的《世界军事》杂志上读到一篇《欧洲的重组》了解到的:1939年德国和苏联瓜分了波兰,苏联占据了波兰东部的大片领土。后来苏德又打起来,德国战败。战争结束后波兰复国,苏联没打算把他们1939年占领的波兰领土还回去,而是将德国东部的大片领土交给波兰作为补偿。所以,1939年的波兰城市利沃夫1945年成了苏联乌克兰加盟共和国城市(是的,就是俄乌战争期间大体安全,偶尔被轰炸的利沃夫),1939年的但泽自由市成了1945年的波兰格但斯克,1939年的东普鲁士哥尼斯堡变成了1945年的苏联俄罗斯加盟共和国(现俄罗斯联邦)城市加里宁格勒。 (function() { var mapElementId = 'post-map-poland'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'lviv', 'kaliningrad', 'gdansk' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { 'cabinda': "卡宾达,属于安哥拉,以前是葡属刚果。", 'cayenne': "卡宴,法属圭亚那首府", 'hans_island': "汉斯岛,加拿大的第二个陆上邻国在这里", 'monchique_islet': "Monchique Islet,属于葡萄牙亚速尔群岛,一般认为的欧盟最西端。", 'neum': "尼姆,波黑出海口", 'dubrovnik': "杜布罗夫尼克,克罗地亚飞地", }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 波兰的平移 感谢读者耐心地读了这么久,是时候送上个文末彩蛋了:如果读者以后遇到任何加拿大地理方面的问题,那么答案必然是下面三者之一:(A)因为离美国近。(B)因为冷。(C)因为加拿大地盾。如果不能决定,那么答案一定是加拿大地盾(安大略为什么这么平?加拿大地盾。魁北克为什么这么多湖?加拿大地盾。为什么加拿大中间没怎么住人?加拿大地盾)。这是我不时去看Reddit的geography subreddit学到的。不用谢。 写《地趣》之前一周我也写了一下杜布罗夫尼克,所以当时提这个出海口的问题也算是recency bias了。 ↩

2025/2/28
articleCard.readMore

租车自驾经验谈(3)

这个题目,我2011年写过一次,2012年又写过一次。时隔十多年我又来唠叨这个话题了,不过准确的标题实应为:“一个外宾的租车体验”。 这次的租车行程是:第一天下午上海浦东机场取车,第二天上午杭州萧山机场还车。租车公司是一嗨,行前通过微信的“出行服务”订的。 (function() { var mapElementId = 'post-map-rent_car_3'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'shanghai_pvg', 'shanghai_fengjing', 'hangzhou_airport' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 租车行程 多年以前我曾经在某个租车公司的浦东机场门店租过车(已经忘记是哪家公司了),当时那家店设在机场区域某个小露天停车场,没有地铁也没有接驳车,需要提前联系门店让工作人员开车过来接。如今我可以坐机场的免费摆渡车,到浦东机场的P4停车场(长期停车场),坐电梯上五楼。 我找了门店的工作人员小哥帮我办手续。由于没有国内手机号,并且我的美国手机号一时开不了热点,这个步骤花了很多时间:小哥需要冻结4000元预授权,Visa信用卡刷不出来;于是小哥让我用支付宝,我连了门店的Wi-Fi,换了若干种登录方式终于登上支付宝,冻结时又需要短信验证,我又要换验证方式,好在终究是弄好了。小哥告诉我:其实如今有很多种方式可以免除预授权冻结(毕竟2024年了),不过前提是,有中国手机号。 预授权弄好小哥就帮我把车开过来,告诉我“在手机上小程序里点一下就可以了”,因为这部车不需要钥匙,完全通过小程序控制。我点了一下,小程序说:“请输入(刚刚给您发送的)验证码”,我和小哥:“呃……” 于是小哥帮我换了一部有实体钥匙的车,解决了这个问题。 与此同时,中国本土的达人门纷纷通过手机上随便点点的方式,把一部又一部车呼呼开走,他们和门店小哥为数不多的几句对白包括:“要看啥证件伐?”(不用)“可以走了是伐?”(对的)。于是在我办租车手续的这半个小时时间里,我是门店里唯一驻足的客人。 我趁着有Wi-Fi,设置好了导航,出了租车点,上了高速,通过收费站,拿了卡:我突然想到另一个问题:我怎么出收费站呢?索性,去下一个服务区找个自动取款机取点现金吧!于是我来到航头服务区,没有找到自动取款机。“没关系!”我想,这个服务区大概比较小比较偏,“我去我熟悉的枫泾服务区,一定有取款机,并且取了钱正好过收费站!”于是在枫泾服务区我停下车,为了保险起见问了在那边工作的人“有没有ATM”,对方:“什么是ATM?”总之,这里也没有。 好消息是,从枫泾出省界,从上海境内进入浙江境内,不需要通过收费站了。我的美国手机号也反应了过来,可以漫游上网了。于是到了杭州出高速的时候,我使用微信支付成功通过了收费站,没有麻烦收费站工作人员给我开热点。这一路导航都在絮叨:前方超速拍照,前方区间测速拍照,前方ETC拍照,前方压线拍照,前方使用手机拍照。高速公路真是适合出片的好地方呢。 萧山机场还车倒是没有花什么时间:开车找到还车的地方,停好车,门店的小哥帮我取消昨天的车辆押金预授权,再帮我刷一个违章预授权,就好了。同时还有两个客人还车,他们都不需要预授权,直接手机上点个按钮,“可以走了是伐?”“对的。”

2025/2/4
articleCard.readMore

蒙塞山怀古

我之前并不知道阿根廷曾经侵略过加利福尼亚,现在你我都知道了。 你是一个乌脖司机,你把乘客送到蒙特雷水族馆,重新起步,离开了那一行罐头厂1,借道大卫街(David Ave)转到灯塔街(Lighthouse Ave)一路向东。开着开着,你看到一个美军基地的牌子,转进那条小路,开上那个小山坡了。你没有直行:那是军事基地正门。你先向左急转弯,再向右急转弯,把车停在了两间小木屋门口,拾级而上,进了右边那一间2。这里有个名字,叫做Presidio of Monterey Museum,蒙特雷要塞博物馆3。 这个小房间里有三个人,正在热烈地聊天,见到我进来,其中两个人马上向第三个人道别,出门去了。于是这第三个人转而和你打了招呼。这是位大爷,穿着浅绿色制服,和国家公园的ranger没有什么两样,胸前也别着姓名牌。你简单答谢之后,他就继续兴致勃勃地讲起“水牛步兵”(Buffalo Soldier)怎么从菲律宾来到这里,讲起他们后来又去优山美地做那边的第一代ranger,讲你没敢进去的军事基地原来的规模,现在的规模,讲蒙特雷之前这里那里的军事基地,并且给你看各种各样的地图,告诉你蒙特雷是多么安全的城市:因为现在仍有相当规模的军队驻扎在这里。二十分钟过去,两个其他什么人走进了这间小木屋,于是大爷去和他们俩打招呼,你的手上多出了一沓打印的资料,以及蒙特雷各个时期的一些地图4。 但你没有夺门而出,因为这里其实布满了展板和陈列品,你却连第一个展板,关于“水牛步兵”的,都还没看完呢。你决定留下来仔细看一看。看过几个展板后,你觉得有张照片上的人像是黎元洪,展板上的文字也有些意思,于是你读了起来:“尔等速降,资财献上;尔若不从,化尔城为齑粉!” 你试着让DeepSeek翻译了一下,但是没采用。 1817年,一天,黎元洪,不,准确地说是伊波利托·布沙尔(Hippolyte de Bouchard),从阿根廷政府5获得了“私掠许可证”,他可以带领战舰,以阿根廷军队的名义,掠夺敌国西班牙6的商船。很快他就指挥一艘“阿根廷号”护卫舰出港抢钱去了:路线是阿根廷——马达加斯加——菲律宾——夏威夷。继续向东航行,1818年11月23日,布沙尔来到蒙特雷要塞外海,向当时属于西班牙帝国领土的蒙特雷发出了那道劝降书。西班牙人没听他的,于是他履行了承诺,夺了城池,竖了旗帜,烧了要塞,偷了牛群,没有伤害普通居民,也没有伤害冯巩。六天后布沙尔带队离开,沿加利福尼亚海岸南下,顺道也侵袭了一些地方。所以,阿根廷确实是侵略过加利福尼亚的,有清嘉庆年间诗句为证:“布沙尔帆船下加州,西班牙王气黯然收”。 他生于法国,不远万里去支持阿根廷人民的革命事业,这是什么精神? 你继续在小木屋看,看到史迪威的照片和展板,原来他也常来这里:1913年他还是个普通军官的时候,曾在蒙特雷要塞服役。1940年他已经是高级将领,又回到蒙特雷扩建要塞,训练军队。后来他从这里出发去中国,之后他和蒋介石的那些事我们就熟悉了,但是展板并没有细说。 他本来打算归隐南边不远的卡梅尔,却在旧金山的Presidio郁郁而终。 已知嘉庆年间蒙特雷属于西班牙而民国初期蒙特雷属于美国,那么蒙特雷是怎么归属美国的呢?你和大爷告别:他们三人还在热络地聊着。你从小木屋走出,沿着小径走到山坡的最高处,这里有一座纪念碑,纪念一个叫Sloat的人。道光26年(1846年),美墨战争真的爆发了7,Sloat出兵从墨西哥8手中夺取了蒙特雷。你原路下山,开车离开,去蒙特雷的历史街区继续访古。但你并不知道,小木屋对面的另一排小木屋背后,有布沙尔袭击蒙特雷的纪念地,阿根廷国旗在那里飘扬,直到你为了把这次的经历写下来去重新看看地图,你才发现你和这里失之交臂。世界上给侵略者立碑纪念的地方不多,你因为错过了这个纪念地感到遗憾。 “人世几回伤往事,山形依旧枕寒流”在这里有独特的应景方式:蒙特雷气候受加利福尼亚寒流影响。 你突然想到,既然道光26年美国才夺取蒙特雷,那么道光24年,还没拥有加利福尼亚的美国,是从哪里派出军舰来到中国,以军舰为后盾,和清朝签署《望厦条约》的呢?欲知后事如何,且听下回分解。 附录 (function() { var mapElementId = 'post-map-monterey'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'location_argentina_flag', 'monterey_presidio', 'monterey_sloat_memorial' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 我还挺喜欢去小众的博物馆,例如这个,现在想必不是那么小众了。 本文当然参考了多篇英文维基百科条目,因此重发我若干年前为维基基金会号召捐款的小文。GenAI时代维基更需要支持。 写作过程中发现了hmdb.org这个网站:hmdb是historic mark database的缩写,这是一个crowdsource的,小众且非营利性的网站,似乎完全靠个人和志愿者维护,可以看到世界各地的历史纪念地的信息和照片,相见恨晚。 其实是那条街叫做Cannery Row,Cannery是罐头厂,Row是“行列”里的行。曾经这条街上有很多罐头厂,斯坦贝克写了一部小说Cannery Row,于是这条街就改名为Cannery Row了。后来这里的罐头厂全部关门了,成了夫子庙田子坊一样的旅游区。 ↩ 左边那一间是卫生间。 ↩ Presidio是西班牙语要塞之意。旧金山也有Presidio,也曾完全是军事要塞。 ↩ 地图太大不好保存,被大爷断舍离了,后来也被你断舍离了。 ↩ 算不算政府,我也不确定,阿根廷1816年宣布独立,但还要两年后他们才赢得独立战争,就像美国从宣布独立到真正独立也花了些时间一样。 ↩ 那时正是玻利瓦尔和圣马丁他们在南美洲轰轰烈烈谋取独立,让西班牙国王焦头烂额的时代。 ↩ 四年前,另一个美国海军将领以为美国和墨西哥爆发战争了,就出兵占领了蒙特雷。后来发现是乌龙,就撤走了,转道去夏威夷帮助了夏威夷王国复国——真是个和假途灭虢相映成趣的好故事。详情。 ↩ 1821年墨西哥独立战争结束,自此蒙特雷归属墨西哥。 ↩

2025/2/2
articleCard.readMore

一个老兵

引子 这不是我第一次写老兵。在二零零几年的时候,我喜欢泡一个军事历史论坛,名字忘记了,只记得和总版主戴良(使用戴安澜照片做头像)和“板油”突击步枪(使用孙立人照片做头像,我自己也使用另一张孙立人照片作为头像)以文会友,竞相发长帖并且频繁讨论。有一次我把我爸当兵时候的一些经历写下来发帖,在论坛上也很受欢迎。不过我没有拿去给我爸看,因为有点不太好意思,想着“老兵不死,只是逐渐凋零”,以后总是有机会的。近二十年过去,老兵并没有凋零,谁知道论坛早早地凋零了,这篇文章(以及在那个论坛上的其他文章)我也找不到了。去年的乌兰故事,其实是第二次写了,显然没什么反响,没有人问过我茅台酒和中华烟之外的那样东西是什么。 言归正传 旧金山湾区的东北一侧,圣弗朗西斯科湾和圣帕博罗湾(San Pablo Bay)之间有座城市,叫做Richmond:这名字如果在弗吉尼亚,就叫里士满;在加拿大温哥华,就叫列治文。其实也可以叫做富山:rich意富,mond意山。第二次世界大战期间,许多妇女走进这里的兵工厂工作,留下了Rosie The Riveter这一美国现代史上的重要篇章,关于这段历史,Richmond有一座不错的小众博物馆,值得一去,这里略过不表。 1940年,有个名叫Bruce Bishofshausen的年轻人,从(亚利桑那州的)迈阿密来到这里,住在Richmond的第六街329号。他网球技术不错,热爱音乐。后来战争打响了,他去德克萨斯州(也是他的祖籍地)Lubbock参加了“陆军空军学校”(Army Air Force Academy),于1942年8月毕业。随即入伍:此时他已经改姓,成为Bruce H. Brown了。他加入了第401轰炸机中队(401st Bombardment Squadron),成为一架B17“空中堡垒”轰炸机的副驾驶,驻扎在英国,参加对德作战。与他同机的还有其他八人。 1942年12月20日,Bruce以及他的战友们从英国剑桥郡驾机出发,去空袭法国境内塞纳河畔罗米伊(Romilly-sur-Seine)的军事目标。不幸的是,他们被纳粹德国的战斗机击落,只有一人幸存,包括Bruce在内的其余八人全部阵亡。他们的遗骨当时被埋葬在当地的墓地,战争结束后被迁葬到位于诺曼底的美国军人公墓(就是《拯救大兵瑞恩》片头,老年瑞恩去祭拜的地方)。不过,当时无法辨认他们的身份,所以他们是作为无名烈士被安葬的。 (function() { var mapElementId = 'post-map-a-veteran-france'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'romilly_sur_seine', 'normandy_american_cemetery' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 又过了二十年,也就是大约1964年,在德克萨斯州,一个十岁的小女孩在她奶奶家发现了一个盒子,里面是她的堂兄(确切地说,是second cousin,有共同的曾祖父母),Bruce Bishofshausen的照片,引发了她的兴趣。不过,家族里没人知道他的具体下落,只知道他很久以前死于飞机坠毁,可能是在北非。小女孩长大后,通过各种方式查询她堂兄的下落,但是一直没有成功。一直到2019年的一天,美国国防部的一名研究员联系了她:”有没有可能Bruce Bishofshausen和Bruce H. Brown是同一个人?”循此线索,当年的小女孩提供了DNA样本,DNA检验的结果表明,在诺曼底安葬的无名烈士中,有一份遗骨确实是属于Bruce的。2023年9月22日,美国国防部负责寻访失踪战士的办公室正式将Bruce的状态更新为”寻获”(accounted for)。在同一天,另外三位Bruce同机的战友(分别来自田纳西,俄亥俄和纽约)也状态更新为”寻获”。仍有三位战友(分别来自新泽西,科罗拉多和南卡罗来纳)的遗骨仍未寻获。 今年十月,老兵Bruce的遗骨回到美国,安葬于休斯顿国家公墓。Richmond市将10月25日设置为他的纪念日。 (function() { var mapElementId = 'post-map-a-veteran-houston'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'houston_national_cemetery' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 写在最后 如果我没有在10月20日下午临时去一下图书馆,并且花了一些时间阅读当天的旧金山论坛报的话,那么我大概率不会读到Bruce的故事并为之动容。今天,11月11日,美国的老兵节,我从纽约时报读到另一个老兵遗骨被辨认的新闻1,又想起这个故事,决定把它写下来。本文参考了旧金山论坛报的报导,美国国防部关于Bruce的页面,以及Richmond市对Bruce的报导。 迄今为止,美国仍有七万两千名二战失踪人员未能寻获。 他叫Jeremiah,列兵,来自芝加哥,有一张娃娃脸。1945年1月在德法边境地区遭突袭阵亡,殁年十九岁。 ↩

2024/11/11
articleCard.readMore

2024年夏·上海

我第一次来上海,是上大学之后的第一个十一假期。记得那次来到了苏州河与黄浦江的交汇处,公交车已经不能再前行,我和几个朋友走过一座桥,挤进汹涌的人潮中,经过外滩、南京东路、人民广场和部分南京西路(路上常常有人挥起充气锤子什么的互相暴揍一顿,有时候把手无寸塑的我们也顺便暴揍一顿),最后在石门一路地铁站附近的一家茶馆通宵打牌度过。大学毕业后到上海工作,没几天我就故地重游,但已经不记得四年前是不是走过了大名鼎鼎的外白渡桥。但是后来,另一个可能性被永久地抹去了。这一回,我和家人也来到了外白渡桥,也沿着外滩、南京东路、人民广场和部分南京西路这样的顺序走。 (function() { var mapElementId = 'post-map-test'; var mapElement = document.getElementById(mapElementId); var maxZoom = 13; // Location IDs needed for this map (generated at build time) var requestedLocationIds = [ 'shanghai_waibaidu', 'shanghai_heping_hotel', 'shanghai_history_museum' ]; // Custom popups and titles (generated at build time) var customPopups = null; var locationTitles = { }; // Mapbox GL version and CDN sources var mapboxVersion = '2.14.1'; var mapboxScriptSources = [ 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.js', 'https://cdn.jsdelivr.net/npm/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js', 'https://unpkg.com/mapbox-gl@' + mapboxVersion + '/dist/mapbox-gl.js' ]; // Load Mapbox GL CSS function loadMapboxCSS() { if (document.querySelector('link[href*="mapbox-gl.css"]')) return; var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://api.mapbox.com/mapbox-gl-js/v' + mapboxVersion + '/mapbox-gl.css'; document.head.appendChild(css); } // Load Mapbox GL JS with fallbacks function loadMapbox(callback, index) { index = index || 0; if (window.mapboxgl) { callback(); return; } if (index >= mapboxScriptSources.length) { callback(new Error('All Mapbox sources failed')); return; } var src = mapboxScriptSources[index]; var script = document.createElement('script'); script.src = src; script.onload = function() { callback(); }; script.onerror = function() { console.warn('Mapbox GL JS load failed from:', src); loadMapbox(callback, index + 1); }; document.head.appendChild(script); } // Get popup content for a location function getPopupContent(loc) { if (customPopups && customPopups[loc.id]) { return customPopups[loc.id]; } else if (locationTitles[loc.id]) { return '' + locationTitles[loc.id] + ''; } else { return '' + loc.name + ''; } } // Initialize Mapbox map function initMapbox(locations) { var MAPBOX_TOKEN = 'pk.eyJ1IjoibGVvbnNvbiIsImEiOiJjbWpraWtqOTUyYnZnM2dvd2thajNtMGZsIn0.08xHonUXcanx3ELmKUv0dg'; if (!MAPBOX_TOKEN) { throw new Error('Missing mapbox_token'); } if (typeof mapboxgl === 'undefined') { throw new Error('mapboxgl not defined'); } if (!mapboxgl.supported()) { throw new Error('Mapbox GL not supported'); } mapboxgl.accessToken = MAPBOX_TOKEN; // Calculate bounds var bounds = new mapboxgl.LngLatBounds(); locations.forEach(function(loc) { bounds.extend([loc.lng, loc.lat]); }); var map = new mapboxgl.Map({ container: mapElementId, style: 'mapbox://styles/mapbox/streets-v12', bounds: bounds, fitBoundsOptions: { padding: 50, maxZoom: maxZoom } }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.on('load', function() { locations.forEach(function(loc) { new mapboxgl.Marker() .setLngLat([loc.lng, loc.lat]) .setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(getPopupContent(loc))) .addTo(map); }); setTimeout(function() { map.resize(); }, 100); }); } // Initialize Leaflet map (fallback) function initLeaflet(locations) { if (typeof L === 'undefined') { mapElement.textContent = 'Map failed to load.'; console.error('Leaflet not available for fallback.'); return; } var bounds = L.latLngBounds(locations.map(function(loc) { return [loc.lat, loc.lng]; })); var map = L.map(mapElementId); map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); locations.forEach(function(loc) { L.marker([loc.lat, loc.lng]) .addTo(map) .bindPopup(getPopupContent(loc)); }); setTimeout(function() { map.invalidateSize(); }, 100); console.warn('Using Leaflet fallback for inline map.'); } // Main initialization document.addEventListener('DOMContentLoaded', function() { fetch('/api/locations.json') .then(function(response) { return response.json(); }) .then(function(locationData) { var locations = []; requestedLocationIds.forEach(function(locId) { if (locationData[locId]) { var locData = locationData[locId]; locations.push({ lat: locData.lat, lng: locData.lng, name: locData.name, id: locId }); } }); if (locations.length === 0) { console.warn('No valid locations found for inline map.'); return; } // Try Mapbox first, fall back to Leaflet loadMapboxCSS(); loadMapbox(function(err) { if (err) { console.warn('Mapbox failed to load, using Leaflet:', err); initLeaflet(locations); return; } try { initMapbox(locations); } catch (e) { console.warn('Mapbox init failed, using Leaflet:', e); initLeaflet(locations); } }); }) .catch(function(error) { console.error('Failed to load location data:', error); mapElement.textContent = 'Failed to load map data.'; }); }); })(); 足迹图 这一路有一些变化:置地广场人去楼空了,对面原本有一家优衣库,如今优衣库没有了,有了各种各样的动漫店,我看不懂,不过很多年轻人来,热闹非凡;以前的上海美术馆,我总是拖着没有去参观,“人民广场这么方便的地方,想去随时就去了”以及“等下次双年展的时候去吧”,谁知后来就没有美术馆了。这次第一次踏进这里,已经变成了历史博物馆。这一路也有不变,例如和平饭店(不过与我对和平饭店的印象有点差距——因为我的印象有点怪异),国营店的沈大成青团,“十里南京路一个新世界”的新世界城商场,以及这次我们住的,已经有点显旧的一家酒店。 二次元入口? 我们没有走到曾经的石门一路站,走回我们住的酒店就结束了。这家酒店对我来说不算陌生了,在上海工作的时候,有一次和一部分同事,跟着我们的“主任”(其实他的正式title是team lead,但是名片上是主任,年龄比我稍大几岁)来过这家酒店,参加一个技术会议。会议的内容已经不太记得了(记得有Chris Richardson来讲CloudFoundry,当时属于VMWare,如今CloudFoundry独立了,VMWare被收购,员工大都被裁了;还有一个来自法国的程序员,还开设了微博叫做“快乐的法国极客”,做了一个讲座,不记得内容了,但是很快他也就不用微博了,或者他从来也没有用过),印象里中午的饭和下午的点心很好吃:毕竟当时没见过世面。我们还碰到另外一个同事:记得他当时刚毕业入职没多久,还不知道可以用公司经费过来,是自己请假自己掏钱来参加的。不过,如今问起他,他已经不记得这个会,也不记得后来有没有让主任给他报销,我也不能问主任了,因为主任已经去世了。 后来我有一次来上海办签证,我们一家就在这家酒店住了,因为离梅龙镇广场的美国签证中心不远。记得那次(2016年12月)有一天晚上在回酒店的路上,在人民广场地铁站九号口,一个男生和一个女生问我骑车到静安寺怎么走,我告诉他们南京西路大概不能骑自行车,需要走那条叫凤阳路的小路往西,他们道谢走了,我心中称奇,路上这么多的人,他们怎么知道我,一个已经离开上海,只是恰好在这里常常骑过车的人,是个适合问路的人呢?我不知道他们那次有没有顺利骑到,后来他们人生路上又去到了哪里。年轻,总是有无限可能。 如果这两个年轻人是从虹口骑过来,那么如果有可能骑过一条叫江西中路的小路。之前我从虹口骑过来的时候,总是过乍浦路桥,总是骑江西中路,道路狭窄,电线密布。如今,这条路两边的房子还是差不多原来的样子,不过有了些新的宣传告示,看起来这边终于“拆到了”。当然,这一片区有许多“优秀历史建筑”的标志牌,我没有注意“优秀历史建筑”会不会对拆迁免疫,不过我知道不是所有房主都乐意自己的房子作为古迹受到保护的。 我拍到的时候,“酝酿期”和第二奖期都已经过去了。 最后来说说我对和平饭店的怪异印象吧。赵薇有一部电影《夜·上海》,是我还在上海时全然被上海主题吸引从头看到尾的。电影的情节诡异,不值得花时间。男主角(一个日本人)晚上从位于陆家嘴金茂大厦的酒店走路溜达出来,在身无分文,没有手机(当时可能还没有IPhone),没有交通卡的情况下,很快在浦西徐汇区汾阳路一带出现,他是怎么渡过黄浦江的呢?后来他和赵薇相遇,在和平饭店背街的巷道里在这座优秀历史建筑的墙上乱涂乱画,也没有惹来任何麻烦,我对影片中优秀历史建筑的遭遇深表痛心。 然而2024年终究还是有人在优秀历史建筑上乱涂乱画了,艺术还真是源于生活啊。 按居住时长来算,上海是我的第三故乡。路过时间很短,也没有特地重游什么故地,但青春记忆随处可见。下次再会。

2024/11/7
articleCard.readMore

写在选举日

今天是2024年11月5日,美国大选日。我开始写下这些文字的时候,美国各地的投票站正在热火朝天地工作。 十二年前的选举季,我们住在宾夕法尼亚州匹兹堡的一座公寓里。有一天一群大学生来敲门,热情洋溢地:“你准备好为奥巴马和拜登投下你们的票,我们一起来改变这个国家了吗?”我们:“呃,我们不是公民,不能投票。”他们依然热情洋溢地:“没关系!请支持奥巴马和拜登!”就去敲下一户门了。后来我们离开了匹兹堡,但是我们的手机号至今仍然是匹兹堡的区号。今年的选举季,我的手机每天都会收到很多条拉票短信。刚开始我见一条删一条,后来我改弦易辙,每条短信都看:我的大略统计是,十条短信里,八条是为特朗普拉票的,一条是看起来为哈里斯拉票但是仔细一看是反讽哈里斯来给特朗普拉票的,还有一条是真的给哈里斯拉票的。我原本以为,这反映了宾夕法尼亚的选战完全被特朗普阵营把持了宣传高地,直到我收到这样一条短信:“凯文啊!作为共和党员你一定要早点出来投票啊!”原来我的手机号的前任主人是注册共和党员,那难怪了。不管怎样,这些短信对于特朗普阵营来说是白发了。 昨天下午,我家也有人敲门,是一个本地候选人,我家人开的门,候选人说了些什么,我家人说“我们已经投过票了。”候选人扭头就走了。我其实是投了这个候选人的,但听说了这事后,一时想去把我的选票找回来重填1。投了票又后悔这样的事,也不鲜见了,例如四年前我们这里投票罢免了之前投票选出来的市议员。又例如今年加州的第36号提案(对于零元购的累犯和拥有毒品的人,让他们更容易进监狱),就是要推翻加州2014年选举的第47号提案。提案的支持方和反对方各有各的道理,各自募集了上千万或数百万的费用。KQED的投票者手册对这个提案,以及选票上的每一个选项都中立而详尽地列举了信息。对于选票上的每一个选项,我也自己做了自己的due diligence来选出最符合我的价值观和利益的选项,之后的事情,就交给程序。 现在是太平洋时间早上九点半。看到的读者朋友如果可以投票但是还没有投票,请出门投票,这很重要。 开玩笑的,选这个候选人也是我做了研究得出的结果,不需要改。 ↩

2024/11/5
articleCard.readMore

三伦行

年初,电影《热辣滚烫》热播,谁能想到我第一次看这部电影,是在从美国到英国的飞机上呢?反正我自己是没有想到。我其实也没有想到今年又会再来一次伦敦,不过最终还是在雇主的business利益驱使下,在亲友后援团的支持下,在九月的一个星期六成行,为期一周。 飞机上看电影 仅仅第三次来,居然已经有了轻车熟路的感觉。航程一路无话1。到了机场我直接买了牡蛎卡以及七日票,跟着标志走去伊丽莎白线,站一路到市中心,下车,出站,找到宾馆签到,扔下行李,转身出门,消失在伦敦茫茫人海中。 追忆口罩末年夏五月2,我还没有阳过。一天在一个视频会议上,话事人说:“我们下个月去伦敦开个三天的会吧!”我一惊:“我没有英国签证!”懂的人说:“那你赶快去办签证?”不懂的人说:“什么叫签证?”很快其他人去聊伦敦的吃喝玩乐去处了,我开始弄签证:需要准备材料需要约时间面谈需要这个需要那个,并且旧金山早已经没有了可以面谈的位置,于是我必须先去到洛杉矶去签证面试,拿到签证才能去伦敦,就如同一个谎言需要另一个谎言来圆,一个出差也需要另一个出差来促成。不过总之business reason合情合理,一笔对个人来说昂贵的“加急签证”费和公司的业务比起来也可以忽略不计,我提前一晚飞去了洛杉矶,赶第二天一大早到VFS的办公室,来了个VIP待遇的签证面谈,秒过,居然还有足够多的时间去Culver City(洛杉矶市区和机场之间的一个近郊城市)看看NPR(美国国家广播公司)西部总部,Culver City downtown溜达一番,以及去附近的Baldwin Hills登了个高,再去赶中午的飞机回湾区。我的护照过了几天寄回给我。 当时拜登政府还有这样的要求:如果从海外来美国,必须有核酸阴性证明。不过,到了我出差的前一周,拜登政府把这个要求取消了,从海外随便回美国,喜大普奔。不过,国门可以进,我也不想带病毒回来进家门,并且之后我们还安排了去总统家参观,他家也还是要求不要带去Covid病毒的,于是我还是带足了口罩,从上飞机开始,我就一直戴着口罩了。 摘掉口罩是在希思罗机场入境的时候了:我小心翼翼地和海关官员沟通,他也很严肃,按照程序问一些问题,后来我回答完一个问题后他说“哦你在Facebook工作啊”就直接敲章放行了,不明觉厉。当然刚刚踏入英国边境的我还是小心翼翼:小心翼翼重新戴上口罩,小心翼翼打Uber,小心翼翼找适当的车门上Uber,小心翼翼下车,小心翼翼办酒店入住,小心翼翼一切。待我出了门,买好地铁票,进入地铁车厢,发现一路上只有我是戴着口罩的。 隔天开始开会,我戴着口罩进去,发现又只有我戴着口罩,我有点感受到压力,默默摘掉了口罩。不过,很快另一位来自湾区的同事3戴着口罩进来,确认过眼神,我又把口罩戴了回去。之后三天的会,我就不再摘口罩了。到了第三天早上,一个英国同事说,今天我有点不舒服,不来开会了。过了一个多小时他又发消息说:“不好!我测了一下好像Covid中招了,你们也都测一下吧!”这一下会议室里热闹了,一大半人从包里掏出了口罩,包括N95口罩,纷纷戴了起来。我和那位湾区同事又确认了一下眼神,我觉得他的眼角闪现了一丝笑意,因为我可能也是。后来听说,其实这个同事没中招,只不过他过了一个多小时才去看测试结果,测试结果早已漫漶了。 不过工作归工作,来到了伦敦还是要尽一个游客的义务的。我自己,以及有的时候和同事,利用伦敦六月日照长且不下雨的特性,以一种特种兵的形式走马观花了一堆地方:维多利亚和艾伯特博物馆、海德公园、绿园、白金汉宫、威斯敏斯特教堂、大本钟、伦敦眼、大英博物馆、大英图书馆、伦敦塔桥、哈利波特站台、唐人街、特拉法尔加广场、皮卡迪利等等。还见了许久未谋面的一位高中同学,听了她的建议,在福南梅森买了很多饼干和一点点茶叶4。 那一周我周五中午的飞机回湾区。出发前的早上我特意去了办公室,可以免费做一个核酸:看看自己有没有中招,可不可以进自家门和拜登家门。做完就奔去机场,一路口罩回家,很快也收到结果了:阴性。后来我在家里打印了核酸结果带去拜登家,他家压根没看。 时间过得很快,去年又来一次,这回是第三次伦敦之行了,所以标题叫做三伦行。这回我第一次去泰特现代艺术馆,第一次看歌剧魅影5,第三次去大英博物馆。第一次去狗牙6排队,第二次去阿森纳礼品店购物,第三次去福南梅森买饼干和茶叶。第一次参观了高中同学家,第一次品尝葡萄牙特色菜:土豆炖和尚鱼。也第一次在伦敦结识新朋友,是在一家新疆菜馆吃饭时的邻桌,他们都来自英国名校,我们相谈甚欢。 第一次去大英博物馆时,最喜欢这位随和的观音菩萨。 第二次去大英博物馆时,印象最深的属于“晚清百态”特展的这幅“平准图”了,每个人都表情各异。 也是上次在大英博物馆看到的黄金丰饶角。今夏在陕西历史博物馆看到类似的兽首玛瑙杯。 这次去大英博物馆我不再特种兵了,仔细看了看钱币展厅。他们是读“开元通宝”的。但我看到这里的大历元宝,对开通元宝的信念更坚定了。 在西安看到过的和同开珎,在这里也看到了。 津巴布韦纸贵:把这些字印在钱币上,比印在纸上便宜。 好久没看到外汇券了。 泰特当代艺术馆的龙猫巴士。 左上角周恩来访问加纳时与恩克鲁玛在他的城堡里打乒乓球的照片,我第一次看到是小时候在《纵横》杂志上,第二次是在泰特当代艺术馆。做裁判的是陈毅和成元功,后来1966年恩克鲁玛在访华期间被政变推翻,成元功文革期间被下放。 其实也有话,因为我一边看《热辣滚烫》空少一边帮我整理小桌板,空少把我的饮料打翻撒我身上了,我当然是选择原谅他了,他和我谁还不是个辛苦打工人呢?并且即使是我自己整理小桌板,我也可能把饮料打翻撒我身上。 ↩ 2022年。当然是事后才知道是末年。小时候看历史年表,佩服那些“后主”,“末帝”都很有预见性,知道自己是最后一代,于是早早管自己叫“后主”。偶尔看到“后主”后面还有“幼主”就嘲笑这次的“后主”估计得不太准。后来才明白这些帝号是后人加的,没有人会管自己叫“前废帝”“东昏侯”这样的。 ↩ 这位同事也是得到了老婆孩子的批准才来。后来他在大裁员的时候中招了,我很想念他。 ↩ Fortnum & Mason,总店在皮卡迪利街,在圣潘克拉斯车站有个小分店,在希思罗三号航站楼有专柜。饼干果然好吃,茶叶也不错——用他们的茶叶做出来的茶叶蛋非常香甜可口。 ↩ 我邻座的老爷爷和老奶奶都戴着口罩,我们还聊了几句。这次我也只在这个剧场看到有人戴口罩,而我自己全程没有戴口罩。 ↩ Goyard,接待我的中东大哥给我推销一个限量款,说买到就是赚到。后来我啥也没买,等以后和老婆一起来再说了。并且,不买立省百分之百。 ↩

2024/9/22
articleCard.readMore