chore(repo): 导入 M-Z 业务底座 V1.9 基线并建立仓库规范

按周工交付的 SEC-Z-01 清理后源码建立 M-Z 主干,并按 Git 仓库惯例重排目录。

- aomin-gateway/  后端工程 87 文件,原样保留,路径与构建命令未变
- demo/           演示前端与设备模拟器 9 文件(原 02_前端与设备端),非正式三端
- docs/handover/  交付与安全清理材料 6 份,内容未改
- docs/contract/  J-00 规格、OpenAPI、内部五接口、状态机、Mock 样例
- docs/reference/ 指纹与校验清单,含 84 个源码文件的 sha256 清单

目录重排只动位置不动内容:源码树 84 文件哈希与交付一致,逐文件自校验通过;
12 条 Flyway 迁移与交付包内镜像逐字节一致。

未纳入:迁移镜像目录(与工程内一套重复,改留指纹)、target/、证据 raw、
应急恢复脚本、评审用虚构台账数据。

安全:SEC-Z-01 15 处明文已清零,导入前重新扫描无新增命中,本仓库无历史污染。
行尾按交付字节原样保存(.gitattributes 设 * -text),不做归一化以免整树摘要失效。
This commit is contained in:
aomin committed 2026-11-13 17:32:37 +08:00
commit a7605ef8b8
121 files changed
+23629

No files matched your search

+422
View File
@@ -0,0 +1,422 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>选择家的位置(离线)</title>
<!--
离线示意底图 · 中山市 · GCJ-02
· 零第三方依赖:不加载任何在线瓦片 / 不请求 SDK / 不需要 key,断网可用;
· 边界为中山市行政区划轮廓(DataV 公开数据,Douglas-Peucker 简化至 206 点),仅作示意;
· 地标为示意位置,实际选点以回传经纬度为准;
· 坐标系 GCJ-02,与腾讯/高德一致,可直接入 household.longitude / latitude。
-->
<style>
:root{--brand:#146b7a;--ink:#1b2430;--sub:#6b7784;--line:#e4eaef}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;height:100%;overflow:hidden}
body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background:#eef2f4;color:var(--ink);font-size:14px;display:flex;flex-direction:column}
.top{flex:0 0 auto;background:var(--brand);color:#fff;padding:11px 12px;display:flex;align-items:center;gap:9px}
.top button{background:rgba(255,255,255,.16);border:0;color:#fff;border-radius:9px;padding:7px 11px;font-size:13px;cursor:pointer}
.top h1{font-size:15px;margin:0;flex:1;font-weight:600}
.stage{flex:1;min-height:200px;position:relative;overflow:hidden}
#cv{display:block;width:100%;height:100%;touch-action:none;cursor:crosshair}
.hud{position:absolute;left:10px;top:10px;background:rgba(255,255,255,.92);border:1px solid var(--line);
border-radius:8px;padding:6px 9px;font-size:11px;color:#5c6b76;line-height:1.55;max-width:64%;pointer-events:none}
.hud b{color:var(--brand)}
.zoom{position:absolute;right:10px;bottom:44px;display:flex;flex-direction:column;gap:6px}
.zoom button{width:34px;height:34px;border:1px solid var(--line);background:rgba(255,255,255,.95);
border-radius:8px;font-size:17px;line-height:1;cursor:pointer;color:var(--ink);padding:0}
.zoom button:active{background:#e8f2f4}
.scalebar{position:absolute;right:10px;bottom:12px;background:rgba(255,255,255,.9);border-radius:6px;
padding:3px 7px;font-size:10.5px;color:#5c6b76;font-family:Consolas,monospace}
.bottom{flex:0 0 auto;background:#fff;border-top:1px solid var(--line);padding:10px 12px 12px}
.addr{font-size:12.5px;color:var(--sub);margin-bottom:6px;min-height:17px;line-height:1.5}
.coord{font-family:Consolas,Menlo,monospace;font-size:11.5px;color:var(--brand);margin-bottom:9px}
.row{display:flex;gap:7px;align-items:center;margin-bottom:8px}
.row input{flex:1;min-width:0;border:1px solid var(--line);border-radius:8px;padding:8px 9px;font-size:12.5px;outline:none}
.row input:focus{border-color:var(--brand)}
.btn{border:0;border-radius:9px;padding:11px 14px;font-size:14px;font-weight:600;cursor:pointer}
.btn-main{background:var(--brand);color:#fff;width:100%}
.btn-main:disabled{background:#c3d3d7;cursor:not-allowed}
.btn-line{background:#fff;border:1px solid var(--line);color:var(--ink);white-space:nowrap}
</style>
</head>
<body>
<div class="top">
<button onclick="cancel()">← 返回</button>
<h1>选择家的位置</h1>
<button onclick="recenter()">回中山</button>
</div>
<div class="stage">
<canvas id="cv"></canvas>
<div class="hud"><b>离线示意底图</b> · 中山市 · GCJ-02<br>拖动平移 · 滚轮/双指缩放 · 点一下落点</div>
<div class="zoom">
<button onclick="zoomBy(1.4)" title="放大">+</button>
<button onclick="zoomBy(1/1.4)" title="缩小">-</button>
</div>
<div class="scalebar" id="sb">—</div>
</div>
<div class="bottom">
<div class="addr" id="addr">点一下地图,落在你家的位置</div>
<div class="coord" id="coord">坐标:未选择(GCJ-02)</div>
<div class="row">
<input id="mlng" placeholder="经度 113.39" inputmode="decimal">
<input id="mlat" placeholder="纬度 22.52" inputmode="decimal">
<button class="btn btn-line" onclick="applyManual()">填入</button>
</div>
<button class="btn btn-main" id="ok" disabled onclick="confirmPick()">确定这个位置</button>
</div>
<script>
/* ---------- 底图数据 ---------- */
var RING = [[113.56875,22.41193],[113.55253,22.41263],[113.53410,22.41640],[113.53042,22.41545],[113.52798,22.41236],[113.52620,22.41180],[113.52365,22.41337],[113.52061,22.41303],[113.51771,22.40835],[113.51432,22.40872],[113.50892,22.41338],[113.50695,22.41329],[113.50372,22.41067],[113.49589,22.40755],[113.49500,22.40566],[113.49523,22.39541],[113.48956,22.39253],[113.48700,22.39020],[113.48588,22.38502],[113.48803,22.38003],[113.49134,22.37694],[113.49130,22.37042],[113.48923,22.36746],[113.48917,22.36582],[113.49244,22.36082],[113.49324,22.35404],[113.48967,22.34973],[113.49155,22.34591],[113.48964,22.34327],[113.49033,22.34072],[113.49478,22.33971],[113.49611,22.33815],[113.49634,22.33370],[113.50074,22.33182],[113.50074,22.32920],[113.50410,22.32431],[113.50313,22.32285],[113.49336,22.31800],[113.49029,22.31784],[113.48686,22.31932],[113.48302,22.31878],[113.48400,22.31703],[113.48202,22.31499],[113.47779,22.31363],[113.47501,22.31493],[113.47048,22.31035],[113.47579,22.30664],[113.47480,22.30521],[113.47939,22.30301],[113.48042,22.30386],[113.48232,22.30099],[113.48292,22.30150],[113.48793,22.29630],[113.48669,22.29544],[113.49139,22.29028],[113.49286,22.28822],[113.49216,22.28750],[113.49639,22.28225],[113.49507,22.27997],[113.49882,22.27468],[113.50126,22.27421],[113.49964,22.27293],[113.50473,22.26698],[113.50826,22.26971],[113.51250,22.26467],[113.51438,22.26154],[113.51303,22.24989],[113.50938,22.25137],[113.50675,22.24673],[113.48628,22.24203],[113.47888,22.23832],[113.48004,22.23364],[113.48614,22.22913],[113.48635,22.22710],[113.47778,22.22498],[113.46353,22.21938],[113.45835,22.21379],[113.44256,22.20705],[113.43260,22.20398],[113.43056,22.20129],[113.42462,22.20104],[113.41576,22.20290],[113.40045,22.22018],[113.39411,22.22897],[113.39036,22.23230],[113.38639,22.23019],[113.37949,22.23591],[113.37693,22.23445],[113.35611,22.24628],[113.34931,22.25263],[113.34287,22.26324],[113.33660,22.28013],[113.32251,22.30867],[113.32130,22.31218],[113.32038,22.32959],[113.31736,22.33573],[113.27383,22.38478],[113.26264,22.39207],[113.25634,22.41081],[113.25587,22.44063],[113.25137,22.44987],[113.24516,22.45727],[113.24015,22.47615],[113.22501,22.49708],[113.21538,22.51310],[113.20940,22.51948],[113.20181,22.52478],[113.19224,22.53551],[113.18711,22.53993],[113.18872,22.55278],[113.18510,22.57429],[113.17640,22.59001],[113.16556,22.60192],[113.16223,22.60966],[113.15701,22.61424],[113.15838,22.61810],[113.15705,22.62357],[113.15722,22.63205],[113.16008,22.64432],[113.16306,22.65136],[113.16967,22.65076],[113.17063,22.65167],[113.16999,22.65870],[113.16617,22.65808],[113.16365,22.66379],[113.16185,22.66349],[113.16084,22.66541],[113.16610,22.66796],[113.16140,22.67370],[113.16408,22.67558],[113.16684,22.67518],[113.17200,22.68067],[113.17315,22.67977],[113.17382,22.68069],[113.17651,22.67911],[113.17318,22.68450],[113.17415,22.68489],[113.17967,22.67882],[113.18182,22.67863],[113.18938,22.67388],[113.19451,22.67541],[113.20104,22.67596],[113.19983,22.67788],[113.19926,22.68254],[113.20437,22.68948],[113.20502,22.69307],[113.20423,22.69732],[113.20955,22.69874],[113.22223,22.71226],[113.23176,22.72820],[113.23632,22.74077],[113.23912,22.74371],[113.24303,22.74535],[113.24807,22.74486],[113.25407,22.74084],[113.26089,22.73033],[113.26392,22.72667],[113.26654,22.72547],[113.27056,22.72648],[113.27828,22.73514],[113.28127,22.73755],[113.28488,22.73888],[113.29124,22.73896],[113.30144,22.73627],[113.31308,22.73764],[113.32912,22.74126],[113.32826,22.74630],[113.33030,22.75051],[113.33746,22.75398],[113.34600,22.76014],[113.35874,22.76487],[113.36516,22.77260],[113.38145,22.76156],[113.41219,22.74283],[113.42614,22.73801],[113.44112,22.73722],[113.44778,22.73585],[113.46797,22.72852],[113.46481,22.72096],[113.47131,22.71499],[113.49191,22.69979],[113.52338,22.67930],[113.54072,22.66621],[113.53951,22.65794],[113.53309,22.65637],[113.53297,22.65498],[113.56163,22.60751],[113.56514,22.60348],[113.56740,22.60593],[113.57192,22.60280],[113.57472,22.60572],[113.57786,22.60391],[113.57855,22.60461],[113.58998,22.59520],[113.59962,22.59439],[113.61501,22.58529],[113.62078,22.57953],[113.63932,22.54828],[113.65161,22.51572],[113.68234,22.51436],[113.69204,22.51515],[113.66843,22.48043],[113.63148,22.47597],[113.61011,22.44571],[113.57372,22.41172],[113.56875,22.41193]];
// 地标:lv1=全域可见(大区域),lv2=放大后才出现(市区细点)
// 坐标为示意位置(误差 1km 内),实际选点以回传经纬度为准
var MARKS = [
{n:"中山市人民政府", lng:113.3926, lat:22.5176, big:1, lv:1},
{n:"小榄镇", lng:113.2450, lat:22.6700, lv:1},
{n:"三乡镇", lng:113.4300, lat:22.3580, lv:1},
{n:"坦洲镇", lng:113.4700, lat:22.2900, lv:1},
{n:"火炬开发区", lng:113.4392, lat:22.5495, lv:1},
{n:"孙文西路步行街", lng:113.3794, lat:22.5186, lv:2},
{n:"中山纪念堂", lng:113.3836, lat:22.5205, lv:2},
{n:"市人民医院", lng:113.3893, lat:22.5245, lv:2},
{n:"中山北站", lng:113.3902, lat:22.5567, lv:2},
{n:"中山站", lng:113.4470, lat:22.5320, lv:2}
];
var DIRS = [
{t:"↑ 广州 / 佛山", lng:113.40, lat:22.80},
{t:"← 江门", lng:113.13, lat:22.57},
{t:"↓ 珠海 / 澳门", lng:113.45, lat:22.17},
{t:"深圳 / 东莞 →", lng:113.72, lat:22.60}
];
/* ---------- 视图 ---------- */
var BBOX = (function(){
var x1=1e9,y1=1e9,x2=-1e9,y2=-1e9;
for(var i=0;i<RING.length;i++){ var p=RING[i];
if(p[0]<x1)x1=p[0]; if(p[0]>x2)x2=p[0];
if(p[1]<y1)y1=p[1]; if(p[1]>y2)y2=p[1]; }
return {cx:(x1+x2)/2, cy:(y1+y2)/2, w:x2-x1, h:y2-y1};
})();
var HOME = {lng:BBOX.cx, lat:BBOX.cy, scale:900};
var view = {lng:HOME.lng, lat:HOME.lat, scale:HOME.scale};
var K = Math.cos(22.5*Math.PI/180);
var cv = document.getElementById('cv'), ctx = cv.getContext('2d');
var W = 0, H = 0, dpr = Math.max(1, Math.min(3, window.devicePixelRatio||1));
var picked = null;
function toScreen(lng, lat){
return [ (lng - view.lng)*view.scale*K + W/2, (view.lat - lat)*view.scale + H/2 ];
}
function toLL(x, y){
return [ view.lng + (x - W/2)/(view.scale*K), view.lat - (y - H/2)/view.scale ];
}
function distKm(a, b){
var dLat=(b.lat-a.lat)*111.32, dLng=(b.lng-a.lng)*111.32*Math.cos((a.lat+b.lat)/2*Math.PI/180);
return Math.sqrt(dLat*dLat + dLng*dLng);
}
/* ---------- 绘制 ---------- */
function draw(){
if(!W) return;
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.clearRect(0,0,W,H);
ctx.fillStyle = '#e9eff1';
ctx.fillRect(0,0,W,H);
drawGrid();
ctx.beginPath();
for(var i=0;i<RING.length;i++){
var p = toScreen(RING[i][0], RING[i][1]);
i ? ctx.lineTo(p[0],p[1]) : ctx.moveTo(p[0],p[1]);
}
ctx.closePath();
ctx.fillStyle = '#f8f6ef';
ctx.fill();
ctx.lineWidth = 1.4; ctx.strokeStyle = '#9fb6b2'; ctx.stroke();
ctx.lineWidth = 5; ctx.strokeStyle = 'rgba(20,107,122,.07)'; ctx.stroke();
drawMarks();
drawDirs();
drawPick();
drawScale();
}
function drawGrid(){
// 网格步长按像素自适应:保证每格 70~140px,避免全览时刻度挤成一团
var steps = [0.005,0.01,0.02,0.05,0.1,0.2,0.5];
var stepDeg = 0.5;
for(var i=0;i<steps.length;i++){
if(steps[i]*view.scale*K >= 72){ stepDeg = steps[i]; break; }
}
var tl = toLL(0,0), br = toLL(W,H);
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(140,160,168,.32)';
ctx.font = '9px Consolas,monospace';
ctx.fillStyle = 'rgba(110,130,140,.6)';
var lastX = -999;
for(var lng=Math.floor(tl[0]/stepDeg)*stepDeg; lng<=br[0]; lng+=stepDeg){
var px = toScreen(lng, 0)[0];
ctx.beginPath(); ctx.moveTo(px,0); ctx.lineTo(px,H); ctx.stroke();
if(px - lastX > 62){ ctx.fillText(lng.toFixed(2)+'°E', px+2, H-3); lastX = px; }
}
var lastY = -999;
for(var lat=Math.floor(br[1]/stepDeg)*stepDeg; lat<=tl[1]; lat+=stepDeg){
var py = toScreen(0, lat)[1];
ctx.beginPath(); ctx.moveTo(0,py); ctx.lineTo(W,py); ctx.stroke();
if(py - lastY > 34){ ctx.fillText(lat.toFixed(2)+'°N', 3, py-3); lastY = py; }
}
}
// 标签避让:放进不来就不画字,只留点
var placed = [];
function overlaps(r){
for(var i=0;i<placed.length;i++){
var p=placed[i];
if(r.x < p.x+p.w && r.x+r.w > p.x && r.y < p.y+p.h && r.y+r.h > p.y) return true;
}
return false;
}
function putLabel(x, y, text, bold, color){
ctx.font = (bold?'600 ':'') + '11px -apple-system,"PingFang SC","Microsoft YaHei",sans-serif';
var w = ctx.measureText(text).width, h = 13;
var tries = [[x+8, y-4], [x-8-w, y-4], [x-w/2, y-19], [x-w/2, y+7]];
for(var i=0;i<tries.length;i++){
var r = {x:tries[i][0]-2, y:tries[i][1]-2, w:w+5, h:h+4};
if(r.x > -2 && r.x+r.w < W+2 && r.y > 0 && r.y+r.h < H-2 && !overlaps(r)){
placed.push(r);
ctx.fillStyle = 'rgba(255,255,255,.86)';
ctx.fillRect(r.x, r.y, r.w, r.h);
ctx.fillStyle = color;
ctx.fillText(text, tries[i][0], tries[i][1]+10);
return true;
}
}
return false;
}
function drawMarks(){
placed = [];
var lv = view.scale > 2400 ? 2 : 1; // 全览只标大区域,放大后才出细地标
for(var i=0;i<MARKS.length;i++){
var m = MARKS[i], p = toScreen(m.lng, m.lat);
if((m.lv||1) > lv) continue;
if(p[0]<-60||p[0]>W+60||p[1]<-30||p[1]>H+30) continue;
var r = m.big ? 5 : 3.6;
ctx.beginPath(); ctx.arc(p[0],p[1],r+2.5,0,7); ctx.fillStyle='rgba(255,255,255,.9)'; ctx.fill();
ctx.beginPath(); ctx.arc(p[0],p[1],r,0,7);
ctx.fillStyle = m.big ? '#c2410c' : '#146b7a'; ctx.fill();
putLabel(p[0], p[1], m.n, !!m.big, m.big ? '#9a3412' : '#334a55');
}
}
function drawDirs(){
ctx.font = '10px -apple-system,"PingFang SC",sans-serif';
ctx.fillStyle = 'rgba(110,130,140,.5)';
ctx.textAlign='center';
for(var i=0;i<DIRS.length;i++){
var d = DIRS[i], p = toScreen(d.lng, d.lat);
if(p[0]>24 && p[0]<W-24 && p[1]>18 && p[1]<H-10) ctx.fillText(d.t, p[0], p[1]);
}
ctx.textAlign='left';
}
function drawPick(){
if(!picked) return;
var p = toScreen(picked.lng, picked.lat);
ctx.strokeStyle='rgba(20,107,122,.35)'; ctx.lineWidth=1;
ctx.beginPath(); ctx.moveTo(p[0]-14,p[1]); ctx.lineTo(p[0]+14,p[1]);
ctx.moveTo(p[0],p[1]-14); ctx.lineTo(p[0],p[1]+14); ctx.stroke();
ctx.save(); ctx.translate(p[0],p[1]);
ctx.beginPath();
ctx.moveTo(0,0); ctx.bezierCurveTo(-11,-9,-11,-22,0,-22); ctx.bezierCurveTo(11,-22,11,-9,0,0);
ctx.closePath(); ctx.fillStyle='#146b7a'; ctx.fill();
ctx.beginPath(); ctx.arc(0,-15,4.2,0,7); ctx.fillStyle='#fff'; ctx.fill();
ctx.restore();
}
function drawScale(){
var perPx = 111320 / view.scale; // 每像素多少米(纬度方向)
var m = perPx * 78;
var nice = [50,100,200,500,1000,2000,5000,10000,20000,50000];
var pick = nice[0];
for(var i=0;i<nice.length;i++){ if(nice[i] <= m) pick = nice[i]; }
var px = Math.round(pick / perPx);
var txt = pick >= 1000 ? (pick/1000)+' km' : pick+' m';
document.getElementById('sb').innerHTML =
'<span style="display:inline-block;width:'+px+'px;height:5px;border:1.5px solid #7d929a;border-top:0;'
+ 'vertical-align:middle;margin-right:6px"></span><span style="vertical-align:middle">'+txt+'</span>';
}
/* ---------- 交互 ---------- */
var fitted = false;
function resize(){
var r = cv.getBoundingClientRect();
W = r.width; H = r.height;
cv.width = Math.round(W*dpr); cv.height = Math.round(H*dpr);
if(!fitted && W > 0 && H > 0){ fitView(); fitted = true; }
draw();
}
window.addEventListener('resize', resize);
// 按视口把整个中山市装进去
function fitView(){
var s = Math.min((W*0.90)/(BBOX.w*K), (H*0.90)/BBOX.h);
HOME = {lng:BBOX.cx, lat:BBOX.cy, scale:clamp(s, 380, 26000)};
view = {lng:HOME.lng, lat:HOME.lat, scale:HOME.scale};
}
var drag = null;
function pos(e){
var r = cv.getBoundingClientRect();
return [ e.clientX - r.left, e.clientY - r.top ];
}
cv.addEventListener('pointerdown', function(e){
cv.setPointerCapture(e.pointerId);
drag = {x:e.clientX, y:e.clientY, lng:view.lng, lat:view.lat, moved:0};
});
cv.addEventListener('pointermove', function(e){
if(!drag) return;
var dx = e.clientX - drag.x, dy = e.clientY - drag.y;
drag.moved = Math.max(drag.moved, Math.abs(dx)+Math.abs(dy));
view.lng = drag.lng - dx/(view.scale*K);
view.lat = drag.lat + dy/view.scale;
draw();
});
cv.addEventListener('pointerup', function(e){
if(!drag) return;
var moved = drag.moved; drag = null;
if(moved > 6) return;
var p = pos(e), ll = toLL(p[0], p[1]);
setPick(ll[0], ll[1]);
});
cv.addEventListener('wheel', function(e){
e.preventDefault();
var p = pos(e);
zoomAt(p[0], p[1], e.deltaY < 0 ? 1.18 : 1/1.18);
}, {passive:false});
var pinch = null;
cv.addEventListener('touchstart', function(e){
if(e.touches.length === 2) pinch = {d: tdist(e), scale: view.scale};
}, {passive:true});
cv.addEventListener('touchmove', function(e){
if(pinch && e.touches.length === 2){
e.preventDefault();
var r = pinch.d>0 ? tdist(e)/pinch.d : 1;
view.scale = clamp(pinch.scale * r, 380, 26000);
draw();
}
}, {passive:false});
cv.addEventListener('touchend', function(){ pinch = null; }, {passive:true});
function tdist(e){
var a=e.touches[0], b=e.touches[1];
return Math.hypot(a.clientX-b.clientX, a.clientY-b.clientY);
}
function clamp(v,a,b){ return Math.max(a, Math.min(b, v)); }
function zoomAt(px, py, k){
var before = toLL(px, py);
view.scale = clamp(view.scale*k, 380, 26000);
var after = toLL(px, py);
view.lng += before[0]-after[0];
view.lat += before[1]-after[1];
draw();
}
function zoomBy(k){ zoomAt(W/2, H/2, k); }
function recenter(){
fitView();
draw();
}
/* ---------- 选点 ---------- */
function setPick(lng, lat){
picked = {lng:lng, lat:lat};
document.getElementById('mlng').value = lng.toFixed(6);
document.getElementById('mlat').value = lat.toFixed(6);
document.getElementById('coord').textContent = '坐标:' + lng.toFixed(6) + ', ' + lat.toFixed(6) + '(GCJ-02)';
document.getElementById('addr').textContent = describe(lng, lat);
document.getElementById('ok').disabled = false;
draw();
}
function nearest(lng, lat){
var best=null, bd=1e9;
for(var i=0;i<MARKS.length;i++){
var d = distKm({lng:lng,lat:lat}, MARKS[i]);
if(d<bd){ bd=d; best=MARKS[i]; }
}
return {m:best, d:bd};
}
function describe(lng, lat){
var r = nearest(lng, lat);
var city = {lng:113.3926, lat:22.5176};
var dc = distKm({lng:lng,lat:lat}, city);
var bearing = azimuth(city, {lng:lng,lat:lat});
var part;
if(r.d < 0.8) part = r.m.n + '附近';
else if(r.d < 6) part = '距' + r.m.n + '约 ' + r.d.toFixed(1) + ' 公里';
else part = '在' + r.m.n + '方向约 ' + r.d.toFixed(1) + ' 公里';
var tail = dc < 0.25 ? '(已选位置)' : '(' + bearing + ',离市政府 ' + dc.toFixed(1) + ' 公里)';
return '中山市 · ' + part + tail;
}
function azimuth(a, b){
var dLng = (b.lng-a.lng)*Math.cos((a.lat+b.lat)/2*Math.PI/180), dLat = b.lat-a.lat;
var ang = Math.atan2(dLng, dLat)*180/Math.PI;
var dirs = ['正北','东北','正东','东南','正南','西南','正西','西北'];
return dirs[Math.round(((ang+360)%360)/45) % 8];
}
function applyManual(){
var lng = parseFloat(document.getElementById('mlng').value);
var lat = parseFloat(document.getElementById('mlat').value);
if(!isFinite(lng) || !isFinite(lat)){ alert('请填入有效的经纬度数字'); return; }
if(lng < 112.5 || lng > 114.5 || lat < 21.5 || lat > 23.5){
alert('这个坐标不在中山附近。\n请填 113.x / 22.x(GCJ-02)。'); return;
}
setPick(lng, lat);
view.lng = lng; view.lat = lat;
if(view.scale < 3000) view.scale = 6000;
draw();
}
/* ---------- 回传 ---------- */
function payload(){
if(!picked) return null;
var r = nearest(picked.lng, picked.lat);
return {
type: 'heserv:pick',
lng: +picked.lng.toFixed(6),
lat: +picked.lat.toFixed(6),
name: r.d < 2 ? r.m.n : '自选位置',
addr: describe(picked.lng, picked.lat),
coordSys: 'GCJ-02',
source: 'offline-zhongshan'
};
}
function confirmPick(){
var d = payload();
if(!d) return;
if(window.parent && window.parent !== window){
window.parent.postMessage(d, '*');
} else {
alert('已选:' + d.lng + ', ' + d.lat + '\n(当前是单独打开,无法回传给主页面)');
}
}
function cancel(){
if(window.parent && window.parent !== window){
window.parent.postMessage({type:'heserv:pick-cancel'}, '*');
} else {
history.back();
}
}
resize();
setTimeout(resize, 60);
</script>
</body>
</html>