diff --git a/tasks/enduro-trails/prototype/app.py b/tasks/enduro-trails/prototype/app.py index 48f4e92..44e7918 100644 --- a/tasks/enduro-trails/prototype/app.py +++ b/tasks/enduro-trails/prototype/app.py @@ -385,38 +385,67 @@ async def get_tile(z: int, x: int, y: int): conn = get_db() cur = conn.cursor() - # Trails — простой bbox по координатам из WKB (без пространственного индекса) - # Используем ST_Intersects если Spatialite доступен, иначе fallback - try: - cur.execute(""" - SELECT osm_id, highway_type, track_type, surface, name, - length_m, mtb_scale, geom - FROM trails - WHERE ST_Intersects(geom, BuildMBR(?,?,?,?,4326)) - LIMIT 5000 - """, (q_west, q_south, q_east, q_north)) - except Exception: - # Fallback без пространственного индекса - cur.execute(""" - SELECT osm_id, highway_type, track_type, surface, name, - length_m, mtb_scale, geom - FROM trails - LIMIT 5000 - """) + # Читаем все trails и фильтруем по bbox через WKB парсинг в Python + # (ST_Intersects не работает с raw WKB без правильной инициализации Spatialite) + cur.execute(""" + SELECT osm_id, highway_type, track_type, surface, name, + length_m, mtb_scale, geom + FROM trails + """) + all_trails = cur.fetchall() - trails_rows = cur.fetchall() + # Фильтруем по bbox через первую точку WKB + trails_rows = [] + for row in all_trails: + geom_blob = row["geom"] + if not geom_blob: + continue + try: + blob = bytes(geom_blob) + endian = "<" if blob[0] == 1 else ">" + gtype = struct.unpack_from(endian + "I", blob, 1)[0] + offset = 5 + if gtype & 0x20000000: + offset += 4 # skip SRID + npts = struct.unpack_from(endian + "I", blob, offset)[0] + offset += 4 + if npts < 2: + continue + # Проверяем первую и последнюю точку для bbox + lon1, lat1 = struct.unpack_from(endian + "dd", blob, offset) + lon2, lat2 = struct.unpack_from(endian + "dd", blob, offset + (npts - 1) * 16) + min_lon = min(lon1, lon2) + max_lon = max(lon1, lon2) + min_lat = min(lat1, lat2) + max_lat = max(lat1, lat2) + if max_lon < q_west or min_lon > q_east or max_lat < q_south or min_lat > q_north: + continue + trails_rows.append(row) + if len(trails_rows) >= 5000: + break + except Exception: + continue - try: - cur.execute(""" - SELECT osm_id, poi_type, name, geom - FROM poi - WHERE ST_Intersects(geom, BuildMBR(?,?,?,?,4326)) - LIMIT 1000 - """, (q_west, q_south, q_east, q_north)) - except Exception: - cur.execute("SELECT osm_id, poi_type, name, geom FROM poi LIMIT 1000") + cur.execute("SELECT osm_id, poi_type, name, geom FROM poi") + all_poi = cur.fetchall() + poi_rows = [] + for row in all_poi: + geom_blob = row["geom"] + if not geom_blob: + continue + try: + blob = bytes(geom_blob) + endian = "<" if blob[0] == 1 else ">" + offset = 5 + gtype = struct.unpack_from(endian + "I", blob, 1)[0] + if gtype & 0x20000000: + offset += 4 + lon, lat = struct.unpack_from(endian + "dd", blob, offset) + if q_west <= lon <= q_east and q_south <= lat <= q_north: + poi_rows.append(row) + except Exception: + continue - poi_rows = cur.fetchall() conn.close() except Exception as e: diff --git a/tasks/enduro-trails/prototype/static/index.html b/tasks/enduro-trails/prototype/static/index.html index 2c1b646..0b1aabd 100644 --- a/tasks/enduro-trails/prototype/static/index.html +++ b/tasks/enduro-trails/prototype/static/index.html @@ -13,8 +13,8 @@ * { box-sizing: border-box; margin: 0; padding: 0; } body { - background: #1a1a2e; - color: #e0e0e0; + background: #f5f3ee; + color: #333333; font-family: 'Segoe UI', system-ui, sans-serif; height: 100vh; display: flex; @@ -22,20 +22,21 @@ } #header { - background: #16213e; - border-bottom: 1px solid #0f3460; + background: #ffffff; + border-bottom: 1px solid #ddd; padding: 10px 16px; display: flex; align-items: center; gap: 16px; flex-shrink: 0; z-index: 10; + box-shadow: 0 1px 4px rgba(0,0,0,0.1); } #header h1 { font-size: 18px; font-weight: 600; - color: #FF8C00; + color: #e07b00; letter-spacing: 0.5px; } @@ -53,9 +54,9 @@ } .toggle-btn { - background: #0f3460; - border: 1px solid #1a5276; - color: #e0e0e0; + background: #f0f0f0; + border: 1px solid #ccc; + color: #444; padding: 5px 12px; border-radius: 4px; cursor: pointer; @@ -66,8 +67,8 @@ gap: 6px; } - .toggle-btn:hover { background: #1a5276; } - .toggle-btn.active { background: #FF8C00; border-color: #FF8C00; color: #1a1a2e; font-weight: 600; } + .toggle-btn:hover { background: #e0e0e0; } + .toggle-btn.active { background: #ff6600; border-color: #ff6600; color: #fff; font-weight: 600; } .dot { width: 10px; height: 10px; @@ -87,13 +88,14 @@ position: absolute; bottom: 30px; left: 12px; - background: rgba(22, 33, 62, 0.92); - border: 1px solid #0f3460; + background: rgba(255,255,255,0.95); + border: 1px solid #ddd; border-radius: 6px; padding: 10px 14px; font-size: 12px; z-index: 5; min-width: 160px; + box-shadow: 0 2px 8px rgba(0,0,0,0.12); } #legend h3 { @@ -120,28 +122,25 @@ .legend-dashed { width: 28px; height: 0; - border-top: 2px dashed #FFD700; + border-top: 2px dashed #cc9900; } /* Popup */ .maplibregl-popup-content { - background: #16213e !important; - color: #e0e0e0 !important; - border: 1px solid #0f3460 !important; + background: #ffffff !important; + color: #333 !important; + border: 1px solid #ddd !important; border-radius: 6px !important; padding: 12px 14px !important; font-size: 13px !important; min-width: 180px; - } - - .maplibregl-popup-tip { - border-top-color: #16213e !important; + box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important; } .popup-title { font-weight: 600; font-size: 14px; - color: #FF8C00; + color: #e07b00; margin-bottom: 6px; } @@ -153,20 +152,21 @@ } .popup-key { color: #888; } - .popup-val { color: #e0e0e0; font-weight: 500; } + .popup-val { color: #333; font-weight: 500; } /* Stats bar */ #stats { position: absolute; top: 10px; right: 12px; - background: rgba(22, 33, 62, 0.85); - border: 1px solid #0f3460; + background: rgba(255,255,255,0.9); + border: 1px solid #ddd; border-radius: 4px; padding: 6px 10px; font-size: 11px; - color: #888; + color: #666; z-index: 5; + box-shadow: 0 1px 4px rgba(0,0,0,0.1); } /* Loading indicator */ @@ -175,20 +175,21 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - background: rgba(22, 33, 62, 0.95); - border: 1px solid #FF8C00; + background: rgba(255,255,255,0.97); + border: 1px solid #ff6600; border-radius: 8px; padding: 20px 30px; text-align: center; z-index: 100; display: none; + color: #333; } #loading.visible { display: block; } #loading .spinner { width: 32px; height: 32px; - border: 3px solid #0f3460; - border-top-color: #FF8C00; + border: 3px solid #eee; + border-top-color: #ff6600; border-radius: 50%; animation: spin 0.8s linear infinite; margin: 0 auto 10px; @@ -202,8 +203,8 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - background: rgba(22, 33, 62, 0.95); - border: 1px solid #FF8C00; + background: rgba(255,255,255,0.97); + border: 1px solid #ff6600; border-radius: 8px; padding: 24px 32px; text-align: center; @@ -213,13 +214,14 @@ } #no-data-warning.visible { display: block; } - #no-data-warning h2 { color: #FF8C00; margin-bottom: 10px; } - #no-data-warning p { color: #aaa; font-size: 13px; line-height: 1.5; } + #no-data-warning h2 { color: #ff6600; margin-bottom: 10px; } + #no-data-warning p { color: #666; font-size: 13px; line-height: 1.5; } #no-data-warning code { - background: #0f3460; + background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: monospace; + color: #333; } @@ -282,7 +284,7 @@
Асфальт -