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:
commit
a7605ef8b8
121 files changed
+23629
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user