Files
aomin a7605ef8b8 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),不做归一化以免整树摘要失效。
2026-11-13 17:32:37 +08:00

423 lines
21 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,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>