x_0: 1500000 → 1496944, y_0: -5911000 → -5911077 Калибровка по 738 permit'ам с кадастровым номером в cad_parcels: до: median≈3055м (систематический сдвиг ~3050м западнее по долготе) после: avg≈14м, median≈7м, p95≈58м Добавлены 4 теста cross-check с эталонными координатами из cad_parcels.geom.
This commit is contained in:
parent
5546056666
commit
b67fa09f1c
2 changed files with 93 additions and 13 deletions
|
|
@ -32,8 +32,9 @@ Format: Form 3 (РНС) + Form 4 (РВЭ) — стандартные Росст
|
|||
16 rve_date — Дата разрешения на ввод
|
||||
|
||||
Координаты raw_coord_x / raw_coord_y — СКФ-66 (МСК-66, Свердловская обл., зона 1).
|
||||
Параметры проекции: tmerc, lon_0=60°E, k=1, false_easting=1500000, false_northing=-5911000,
|
||||
Параметры проекции: tmerc, lon_0=60°E, k=1, false_easting=1496944, false_northing=-5911077,
|
||||
эллипсоид Красовского, сдвиг datum towgs84=23.92,-141.27,-80.9,0,0.35,0.82,-0.12.
|
||||
Параметры откалиброваны по cad_parcels ground-truth (738 точек): avg_err≈14м, median≈7м.
|
||||
Ячейки могут содержать несколько точек через пробел — берём первую.
|
||||
Геокодирование выполняется при парсинге (geom, geocoded_lat/lon).
|
||||
"""
|
||||
|
|
@ -57,12 +58,14 @@ logger = logging.getLogger(__name__)
|
|||
# ── МСК-66 → WGS84 трансформер ───────────────────────────────────────────────
|
||||
# СКФ-66 (местная СК Свердловской обл., зона 1):
|
||||
# tmerc, осевой меридиан 60°E, масштаб 1.0
|
||||
# ложный восток 1 500 000 м, ложный север -5 911 000 м (эмпирически подобран по
|
||||
# нескольким объектам с известными адресами: Кольцово, пр. Космонавтов и др.)
|
||||
# x_0=1496944 (false_easting), y_0=-5911077 (false_northing)
|
||||
# Откалиброваны по cad_parcels ground-truth (738 точек permit'ов с кад.номером):
|
||||
# avg_err≈14м, median≈7м, p95≈58м (без 8 записей с ошибочными Y в исходных данных).
|
||||
# Предыдущее x_0=1500000 давало систематический сдвиг ~3050м западнее по долготе.
|
||||
# эллипсоид Красовского + сдвиг Пулково→WGS84
|
||||
_MSK66_PROJ4 = (
|
||||
"+proj=tmerc +lat_0=0 +lon_0=60 +k=1"
|
||||
" +x_0=1500000 +y_0=-5911000"
|
||||
" +x_0=1496944 +y_0=-5911077"
|
||||
" +ellps=krass +towgs84=23.92,-141.27,-80.9,0,0.35,0.82,-0.12"
|
||||
" +units=m +no_defs"
|
||||
)
|
||||
|
|
@ -91,8 +94,8 @@ def msk66_to_wgs84(raw_x: str | None, raw_y: str | None) -> tuple[float, float]
|
|||
или результат выходит за пределы ЕКБ-региона.
|
||||
|
||||
В МСК-66 Свердловской обл.:
|
||||
raw_coord_x ≈ 1 500 000 + смещение (false_easting = 1 500 000)
|
||||
raw_coord_y ≈ northing - 5 911 000 (false_northing = -5 911 000)
|
||||
raw_coord_x ≈ 1 500 000 + смещение (откалиброванный false_easting = 1 496 944)
|
||||
raw_coord_y ≈ northing + смещение (откалиброванный false_northing = -5 911 077)
|
||||
"""
|
||||
if not raw_x or not raw_y:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -363,18 +363,95 @@ class TestSslConfiguration:
|
|||
|
||||
# ── unit tests: msk66_to_wgs84 ───────────────────────────────────────────────
|
||||
|
||||
# Параметры допуска для cad_parcels cross-check (≈50м по Хаверсину)
|
||||
# На широте ЕКБ (~56.9°): 1° широты ≈ 111320м, 1° долготы ≈ 60730м
|
||||
# 50м ÷ 111320 ≈ 0.00045° по широте; 50м ÷ 60730 ≈ 0.00082° по долготе
|
||||
_TOL_LAT_DEG = 0.00045 # ~50м по широте
|
||||
_TOL_LON_DEG = 0.00082 # ~50м по долготе
|
||||
|
||||
|
||||
class TestMsk66ToWgs84:
|
||||
"""Тесты трансформации МСК-66 → WGS84.
|
||||
|
||||
Реальные данные из ekburg_construction_permits:
|
||||
X=1534814.7997, Y=394813.2001 — пр. Космонавтов 18, ЕКБ (~56.87N 60.57E)
|
||||
X=1544026.7997, Y=381585.4401 — район аэропорта Кольцово (~56.74N 60.72E)
|
||||
X=1526550.1397, Y=387129.3801 — ул. Смородиновая 187, западная часть ЕКБ
|
||||
Реальные данные из ekburg_construction_permits (сверены с cad_parcels ground-truth).
|
||||
Калиброванный proj4: x_0=1496944, y_0=-5911077
|
||||
— avg_err≈14м, median≈7м, p95≈58м на 738 точках с кадастровым номером.
|
||||
|
||||
Точки для cross-check получены из cad_parcels.geom (ST_Centroid) и проверены SQL:
|
||||
X=1534814.7997, Y=394813.2001 → lon≈60.619637, lat≈56.871948
|
||||
(cad 66:41:0110016:1046, погрешность <50м от центроида участка)
|
||||
X=1544026.7997, Y=381585.4401 → lon≈60.768255, lat≈56.752330
|
||||
(cad 66:41:0503018:248, аэропорт Кольцово)
|
||||
X=1526550.1397, Y=387129.3801 → lon≈60.483224, lat≈56.803547
|
||||
(cad 66:41:0306057:89, западная часть ЕКБ)
|
||||
X=1531655.2797, Y=398430.2401 → lon≈60.568320, lat≈56.904674
|
||||
(cad 66:41:0106051:10, ул. Республиканская 1а; err_m=12)
|
||||
"""
|
||||
|
||||
def test_kosmonavtov_cad_crosscheck(self) -> None:
|
||||
"""X=1534814.7997/Y=394813.2001 — сверка с centroid cad_parcels 66:41:0110016:1046.
|
||||
|
||||
Ожидаемые координаты получены из PostGIS: lon≈60.619637, lat≈56.871948.
|
||||
Допуск ±0.00082° по долготе (~50м), ±0.00045° по широте (~50м).
|
||||
"""
|
||||
result = msk66_to_wgs84("1534814.7997", "394813.2001")
|
||||
assert result is not None
|
||||
lon, lat = result
|
||||
assert (
|
||||
abs(lon - 60.619637) < _TOL_LON_DEG
|
||||
), f"lon={lon:.6f} далеко от эталонного 60.619637 (Δ={abs(lon - 60.619637):.6f}°)"
|
||||
assert (
|
||||
abs(lat - 56.871948) < _TOL_LAT_DEG
|
||||
), f"lat={lat:.6f} далеко от эталонного 56.871948 (Δ={abs(lat - 56.871948):.6f}°)"
|
||||
|
||||
def test_koltsovo_cad_crosscheck(self) -> None:
|
||||
"""X=1544026.7997/Y=381585.4401 — сверка с centroid cad_parcels 66:41:0503018:248.
|
||||
|
||||
Ожидаемые координаты: lon≈60.768255, lat≈56.752330 (аэропорт Кольцово).
|
||||
"""
|
||||
result = msk66_to_wgs84("1544026.7997", "381585.4401")
|
||||
assert result is not None
|
||||
lon, lat = result
|
||||
assert (
|
||||
abs(lon - 60.768255) < _TOL_LON_DEG
|
||||
), f"lon={lon:.6f} далеко от эталонного 60.768255 (Δ={abs(lon - 60.768255):.6f}°)"
|
||||
assert (
|
||||
abs(lat - 56.752330) < _TOL_LAT_DEG
|
||||
), f"lat={lat:.6f} далеко от эталонного 56.752330 (Δ={abs(lat - 56.752330):.6f}°)"
|
||||
|
||||
def test_smorodinovaya_cad_crosscheck(self) -> None:
|
||||
"""X=1526550.1397/Y=387129.3801 — сверка с centroid cad_parcels 66:41:0306057:89.
|
||||
|
||||
Ожидаемые координаты: lon≈60.483224, lat≈56.803547 (западная часть ЕКБ).
|
||||
"""
|
||||
result = msk66_to_wgs84("1526550.1397", "387129.3801")
|
||||
assert result is not None
|
||||
lon, lat = result
|
||||
assert (
|
||||
abs(lon - 60.483224) < _TOL_LON_DEG
|
||||
), f"lon={lon:.6f} далеко от эталонного 60.483224 (Δ={abs(lon - 60.483224):.6f}°)"
|
||||
assert (
|
||||
abs(lat - 56.803547) < _TOL_LAT_DEG
|
||||
), f"lat={lat:.6f} далеко от эталонного 56.803547 (Δ={abs(lat - 56.803547):.6f}°)"
|
||||
|
||||
def test_respublikanskaya_cad_crosscheck(self) -> None:
|
||||
"""X=1531655.2797/Y=398430.2401 — ул. Республиканская 1а, cad 66:41:0106051:10.
|
||||
|
||||
Ожидаемые координаты: lon≈60.568320, lat≈56.904674 (err_m=12 по cad_parcels).
|
||||
Этот тест ловит регрессию easting-сдвига: при x_0=1500000 computed был бы ~60.519°.
|
||||
"""
|
||||
result = msk66_to_wgs84("1531655.2797", "398430.2401")
|
||||
assert result is not None
|
||||
lon, lat = result
|
||||
assert (
|
||||
abs(lon - 60.568320) < _TOL_LON_DEG
|
||||
), f"lon={lon:.6f} далеко от эталонного 60.568320 (Δ={abs(lon - 60.568320):.6f}°)"
|
||||
assert (
|
||||
abs(lat - 56.904674) < _TOL_LAT_DEG
|
||||
), f"lat={lat:.6f} далеко от эталонного 56.904674 (Δ={abs(lat - 56.904674):.6f}°)"
|
||||
|
||||
def test_kosmonavtov_in_ekb(self) -> None:
|
||||
"""Пр. Космонавтов 18 — попадает в ЕКБ-регион."""
|
||||
"""Пр. Космонавтов — попадает в ЕКБ-регион."""
|
||||
result = msk66_to_wgs84("1534814.7997", "394813.2001")
|
||||
assert result is not None
|
||||
lon, lat = result
|
||||
|
|
@ -426,7 +503,7 @@ class TestMsk66ToWgs84:
|
|||
|
||||
def test_out_of_range_x_returns_none(self) -> None:
|
||||
"""X вне диапазона МСК-66 (например, случайное число) → None."""
|
||||
# X=500000 — это не МСК-66 (false_easting=1500000, диапазон 1450000-1600000)
|
||||
# X=500000 — это не МСК-66 (false_easting ~1500000, диапазон 1450000-1600000)
|
||||
result = msk66_to_wgs84("500000.0", "380000.0")
|
||||
assert result is None
|
||||
|
||||
|
|
@ -441,4 +518,4 @@ class TestMsk66ToWgs84:
|
|||
# Проверяем что первый элемент — долгота (60.x), второй — широта (56.x)
|
||||
assert 56.0 < lat < 58.0, f"ожидали широту, получили {lat}"
|
||||
assert 59.0 < lon < 62.0, f"ожидали долготу, получили {lon}"
|
||||
assert lon > lat # lon ~60.5 > lat ~56.9
|
||||
assert lon > lat # lon ~60.6 > lat ~56.9
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue