Files
ios-location-track/core/protocol.py
T
jbjf6666 c60e4b5cd4 chore(release): v1.1.0 — 版本号 1.0.0 → 1.1.0,README 双语化,构建脚本编码自检
版本号 1.1.0(Windows 支持是新功能)
- core/protocol.py APP_VERSION、installer/version_info.txt、installer/iosloc.iss
  MyAppVersion、Location.spec(mac BUNDLE)、tools/make_dmg.py VERSION 全部同步到 1.1.0
- 产物命名规范:Windows 用 _win、macOS 用 _mac 后缀,Release 页面一眼分清平台
  · iOSLocationTrack-1.1.0_win-setup.exe
  · iOSLocationTrack-1.1.0_win-portable.zip
  · iOSLocationTrack-1.1.0_mac.dmg

README 双语化(默认中文)
- docs/README-WINDOWS.en.md 新增(Windows 文档全文英文版)
- docs/README-WINDOWS.md 顶部加 [中文] | [English] 语言切换
- README.md / README.en.md 的下载章节改成平台表格,说明 _win / _mac 命名

构建脚本编码自检
- scripts/build.ps1 新增第 0.5 步:检查 scripts/*.ps1 是否为 UTF-8 with BOM,
  缺失则自动补回。Windows PowerShell 5.1 没有 BOM 时会按 ANSI(GBK) 解析,
  中文乱码且直接语法报错(改个注释就构建不了,本项目实际踩过两次)
- 本次修复:build.ps1 的 BOM 在编辑后被工具静默丢弃,已补回

验收(Windows 11 x64 / iPhone 11 iOS 26.6.1)
- selftest 83/83、drift_sim 40/40、offline_tiles 13/13
- 打包版 exe 清单含 requireAdministrator,FileVersion 1.1.0.0
2026-10-16 01:51:50 +08:00

161 lines
4.5 KiB
Python
Raw 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.
"""公共常量与工具:前后端接口契约见 docs/PROTOCOL.md。
本模块不依赖任何第三方库。
"""
from __future__ import annotations
import json
import math
import time
import uuid
from typing import Any
APP_NAME = "iOS 虚拟定位轨迹工具"
APP_VERSION = "1.1.0"
PROTOCOL_VERSION = "v1"
DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 8787
# 地理常量
EARTH_RADIUS_M = 6371008.8
# 播放引擎默认参数
DEFAULT_SEND_HZ = 5.0
MIN_SEND_HZ = 1.0
MAX_SEND_HZ = 20.0
MODE_DEFAULTS = {
"driving": 13.9, # m/s ≈ 50 km/h
"cycling": 5.5, # m/s ≈ 20 km/h
"walking": 1.4, # m/s ≈ 5 km/h
"flight": 60.0, # m/s ≈ 216 km/h
}
MODE_LABELS = {
"driving": "驾车",
"cycling": "骑行",
"walking": "步行",
"flight": "飞行",
}
MIN_SPEED_SCALE = 0.1
MAX_SPEED_SCALE = 50.0
# 单点定位与轨迹点的合法范围
MIN_LAT, MAX_LAT = -85.05112878, 85.05112878
MIN_LNG, MAX_LNG = -180.0, 180.0
class AppError(Exception):
"""带机器码与中文提示的业务异常。"""
def __init__(self, message: str, code: str = "app_error", status: int = 400) -> None:
super().__init__(message)
self.message = message
self.code = code
self.status = status
def to_dict(self) -> dict[str, Any]:
return {"ok": False, "error": self.message, "code": self.code}
def now_ts() -> float:
return time.time()
def round6(value: float) -> float:
return round(float(value), 6)
def clamp(value: float, low: float, high: float) -> float:
"""夹取到 [low, high]。NaN 会直接抛错——NaN 参与比较恒为 False,会静默污染整条链路。"""
if not math.isfinite(value):
raise AppError("数值不是有限数(不允许 NaN / Infinity)", "bad_number")
if value < low:
return low
if value > high:
return high
return value
def new_id(prefix: str) -> str:
return f"{prefix}-{uuid.uuid4().hex[:10]}"
def dumps(obj: Any) -> str:
"""序列化为 JSON。
allow_nan=False:任何 NaN/Infinity 都会抛错而不是产出 `NaN` 字面量——
后者会让浏览器 JSON.parse 直接失败,把整帧数据丢掉。
"""
try:
return json.dumps(
obj, ensure_ascii=False, separators=(",", ":"), default=_json_default, allow_nan=False
)
except ValueError as exc:
raise AppError(f"内部数据包含非法数值(NaN/Infinity),已拒绝下发:{exc}", "bad_number") from exc
def finite(value: Any, field: str = "数值") -> float:
"""校验并返回有限浮点数。"""
try:
number = float(value)
except (TypeError, ValueError) as exc:
raise AppError(f"{field}必须是数字", "bad_number") from exc
if not math.isfinite(number):
raise AppError(f"{field}不是有限数(不允许 NaN / Infinity)", "bad_number")
return number
def _json_default(obj: Any) -> Any:
if isinstance(obj, (set, tuple)):
return list(obj)
if hasattr(obj, "isoformat"):
return obj.isoformat()
return str(obj)
def loads(raw: str | bytes) -> Any:
if isinstance(raw, bytes):
raw = raw.decode("utf-8", "replace")
return json.loads(raw)
def validate_latlng(lat: Any, lng: Any) -> tuple[float, float]:
"""校验并归一化经纬度,返回 (lat, lng)。"""
try:
flat = float(lat)
flng = float(lng)
except (TypeError, ValueError) as exc:
raise AppError("经纬度必须是数字", "bad_coords") from exc
if math.isnan(flat) or math.isnan(flng) or math.isinf(flat) or math.isinf(flng):
raise AppError("经纬度不是有效数字", "bad_coords")
if not (MIN_LAT <= flat <= MAX_LAT):
raise AppError(f"纬度超出有效范围({MIN_LAT:.4f} ~ {MAX_LAT:.4f})", "bad_coords")
if not (MIN_LNG <= flng <= MAX_LNG):
raise AppError("经度超出有效范围(-180 ~ 180)", "bad_coords")
return flat, flng
def validate_speed_scale(value: Any) -> float:
scale = finite(value, "倍速")
if scale <= 0:
raise AppError("倍速必须大于 0", "bad_speed")
if not (MIN_SPEED_SCALE <= scale <= MAX_SPEED_SCALE):
raise AppError(
f"倍速需在 {MIN_SPEED_SCALE} ~ {MAX_SPEED_SCALE} 之间", "bad_speed"
)
return scale
def validate_mode(mode: Any) -> str:
if mode is None:
return "driving"
text = str(mode).strip().lower()
if text not in MODE_DEFAULTS:
raise AppError(
"出行方式只支持 driving / cycling / walking / flight", "bad_mode"
)
return text