Files
CM95/web/mysql_postgresql_compare_north_kyushu.html
2026-10-02 18:04:53 +08:00

62 lines
7.0 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>CM95 MySQL vs PostgreSQL PBF 对比</title>
<link rel="stylesheet" href="/assets/maplibre-gl.css">
<style>
*{box-sizing:border-box}html,body{height:100%;margin:0;background:#111;color:#eee;font-family:system-ui,-apple-system,"Segoe UI",sans-serif}
body{overflow:hidden}#bar{height:52px;display:flex;align-items:center;gap:12px;padding:0 12px;background:#17191d;border-bottom:1px solid #3b3f46;font-size:12px}
#bar strong{font-size:14px;white-space:nowrap}#bar button{border:1px solid #555;background:#272b31;color:#fff;border-radius:5px;padding:5px 9px;cursor:pointer}
#bar label{display:flex;gap:5px;align-items:center;white-space:nowrap}.muted{color:#aeb7c3}#readout{margin-left:auto;font-variant-numeric:tabular-nums;white-space:nowrap}
#maps{height:calc(100% - 52px);display:grid;grid-template-columns:1fr 1fr;gap:2px;background:#555}
.pane{position:relative;min-width:0;overflow:hidden;background:#d8edf5}.map{position:absolute;inset:0}
.title{position:absolute;z-index:5;left:10px;top:10px;padding:7px 9px;border-radius:6px;background:rgba(18,20,24,.88);font-size:12px;line-height:1.4}
.title b{display:block;font-size:14px}.state{font-size:11px;color:#cbd2dc}.ok{color:#91e39b}.err{color:#ff8c8c}
.inspect{position:absolute;z-index:5;left:10px;right:10px;bottom:10px;max-height:28%;overflow:auto;padding:7px 9px;border-radius:6px;background:rgba(18,20,24,.9);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap}
#stats{position:absolute;z-index:10;top:62px;left:50%;transform:translateX(-50%);max-width:72%;padding:7px 10px;border-radius:6px;background:rgba(18,20,24,.9);font:11px/1.35 ui-monospace,SFMono-Regular,Consolas,monospace;pointer-events:none}
@media(max-width:820px){#maps{grid-template-columns:1fr;grid-template-rows:1fr 1fr}#bar .muted{display:none}#stats{max-width:94%}}
</style>
</head>
<body>
<div id="bar"><strong>CM95 PBF:MySQL vs PostgreSQL(北九州人工对比)</strong><span class="muted">同源数据 / 同一 S-52 style / 同步视图</span>
<label><input id="sync" type="checkbox" checked>同步</label><button id="reset">差异 Tile</button><button id="info">统计</button><span id="readout">--</span></div>
<div id="stats" hidden></div>
<div id="maps">
<section class="pane"><div id="mysql-map" class="map"></div><div class="title"><b>左:MySQL</b><span id="mysql-state" class="state">加载中…</span></div><div id="mysql-inspect" class="inspect">点击任一侧,同点检查 Feature。</div></section>
<section class="pane"><div id="pg-map" class="map"></div><div class="title"><b>右:PostgreSQL / PostGIS</b><span id="pg-state" class="state">加载中…</span></div><div id="pg-inspect" class="inspect">点击任一侧,同点检查 Feature。</div></section>
</div>
<script src="/assets/maplibre-gl.js"></script>
<script>
const BOUNDS=[[130.25390625,33.57801474614399],[130.4296875,33.7243396617476]], START={center:[130.341796875,33.65117720394579],zoom:11.25,bearing:0,pitch:0};
const sync=document.getElementById("sync"), readout=document.getElementById("readout"), stats=document.getElementById("stats");
let styleTemplate=null,syncing=false,m1=null,m2=null;
function state(id,text,ok=true){const e=document.getElementById(id);e.textContent=text;e.className="state "+(ok?"ok":"err")}
function blank(){return {version:8,sources:{},layers:[{id:"bg",type:"background",paint:{"background-color":"#d8edf5"}}]}}
function styleFor(kind){const s=JSON.parse(JSON.stringify(styleTemplate));const src=s.sources&&s.sources.cm95;if(!src)throw new Error("style 缺少 sources.cm95");
src.type="vector";src.tiles=[location.origin+"/"+kind+"/{z}/{x}/{y}.pbf"];src.minzoom=5;src.maxzoom=12;src.bounds=[128,29.5,132.5,34.8];delete src.url;s.sprite=location.origin+"/sprite";return s}
const mysqlMap=new maplibregl.Map({container:"mysql-map",style:blank(),...START,attributionControl:false});
const pgMap=new maplibregl.Map({container:"pg-map",style:blank(),...START,attributionControl:false});
mysqlMap.addControl(new maplibregl.NavigationControl(),"top-right");pgMap.addControl(new maplibregl.NavigationControl(),"top-right");
function camera(m){const c=m.getCenter();return {center:[c.lng,c.lat],zoom:m.getZoom(),bearing:m.getBearing(),pitch:m.getPitch()}}
function link(a,b){if(syncing||!sync.checked)return;syncing=true;try{b.jumpTo(camera(a))}finally{syncing=false}}
mysqlMap.on("move",()=>{link(mysqlMap,pgMap);showReadout(mysqlMap)});pgMap.on("move",()=>{link(pgMap,mysqlMap);showReadout(pgMap)});
function showReadout(m){const c=m.getCenter();readout.textContent="z="+m.getZoom().toFixed(2)+" "+c.lng.toFixed(5)+", "+c.lat.toFixed(5)}
document.getElementById("reset").onclick=()=>{mysqlMap.fitBounds(BOUNDS,{padding:36,duration:0});link(mysqlMap,pgMap)};
document.getElementById("info").onclick=()=>{stats.hidden=!stats.hidden};
async function load(){try{const [sr,ir]=await Promise.all([fetch("/style.json",{cache:"no-store"}),fetch("/api/info",{cache:"no-store"})]);styleTemplate=await sr.json();const info=await ir.json();
stats.textContent="MySQL: "+info.mysql.release+" | "+info.mysql.files+" tiles\nPostgreSQL: "+info.postgresql.release+" | "+info.postgresql.files+" tiles\nCommon tile hash: "+info.compare.identical+"/"+info.compare.common+" identical";
mysqlMap.setStyle(styleFor("mysql"));pgMap.setStyle(styleFor("postgres"));}catch(e){state("mysql-state",String(e),false);state("pg-state",String(e),false)}}
mysqlMap.on("style.load",()=>state("mysql-state","S-52 / MySQL PBF",true));pgMap.on("style.load",()=>state("pg-state","S-52 / PostgreSQL PBF",true));
function key(f){const p=f.properties||{};return [f.sourceLayer||"",f.id??"",p.canonical_object_type||"",p.source_object_class||""].join("|")}
function summary(f){const p=f.properties||{};return [f.sourceLayer||"-","id="+(f.id??"-"),p.canonical_object_type||"",p.source_object_class||"",p.chart_label_text||"",p.contour_depth_m!==undefined?"depth="+p.contour_depth_m:""].filter(Boolean).join(" ")}
function inspect(map,ll,id){const el=document.getElementById(id);if(!map.loaded()){el.textContent="地图加载中";return}const p=map.project(ll),box=[[p.x-5,p.y-5],[p.x+5,p.y+5]],u=new Map();
for(const f of map.queryRenderedFeatures(box)){if(!u.has(key(f)))u.set(key(f),f)}const a=[...u.values()].slice(0,24);el.textContent=ll.lng.toFixed(6)+", "+ll.lat.toFixed(6)+"\n命中 "+u.size+" 项\n"+a.map((f,i)=>String(i+1).padStart(2,"0")+". "+summary(f)).join("\n")}
function compare(ll){if(m1)m1.remove();if(m2)m2.remove();m1=new maplibregl.Marker().setLngLat(ll).addTo(mysqlMap);m2=new maplibregl.Marker().setLngLat(ll).addTo(pgMap);inspect(mysqlMap,ll,"mysql-inspect");inspect(pgMap,ll,"pg-inspect")}
mysqlMap.on("click",e=>compare(e.lngLat));pgMap.on("click",e=>compare(e.lngLat));
mysqlMap.on("error",e=>{const m=e?.error?.message||"";if(!m.includes("404"))state("mysql-state",m,false)});pgMap.on("error",e=>{const m=e?.error?.message||"";if(!m.includes("404"))state("pg-state",m,false)});
window.addEventListener("resize",()=>{mysqlMap.resize();pgMap.resize()});showReadout(mysqlMap);load();
</script>
</body></html>