62 lines
7.0 KiB
HTML
62 lines
7.0 KiB
HTML
<!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>
|