mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-22 09:42:10 +08:00
Young Cheddar
This commit is contained in:
@@ -344,6 +344,8 @@ class LatControlTorque(LatControl):
|
||||
elif kia_carnival_active:
|
||||
friction_threshold = get_kia_carnival_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk)
|
||||
friction_scale *= get_kia_carnival_friction_center_fade_scale(setpoint, CS.vEgo)
|
||||
elif tucson_4th_gen_active:
|
||||
friction_threshold = get_tucson_4th_gen_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk)
|
||||
elif self.is_silverado:
|
||||
ff *= silverado_center_taper
|
||||
elif volt_plexy_test_active:
|
||||
|
||||
@@ -351,6 +351,7 @@ TUCSON_4TH_GEN_CENTER_TAPER_LAT = 0.28
|
||||
TUCSON_4TH_GEN_CENTER_TAPER_LAT_WIDTH = 0.055
|
||||
TUCSON_4TH_GEN_CENTER_TAPER_SPEED_MAX = 14.0
|
||||
TUCSON_4TH_GEN_CENTER_TAPER_SPEED_WIDTH = 1.5
|
||||
TUCSON_4TH_GEN_FRICTION_THRESHOLD_GAIN = 0.22
|
||||
|
||||
KIA_FORTE_BASE_LAT_ACCEL_FACTOR_MULT = 1.05
|
||||
KIA_FORTE_FF_REDUCTION_LEFT = 0.05
|
||||
@@ -1645,12 +1646,24 @@ def get_kia_carnival_friction_center_fade_scale(desired_lateral_accel: float, v_
|
||||
return 1.0 - (KIA_CARNIVAL_FRICTION_CENTER_FADE_MAX * speed_weight * center_weight)
|
||||
|
||||
|
||||
def get_tucson_4th_gen_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
|
||||
def _tucson_4th_gen_center_weights(desired_lateral_accel: float, v_ego: float) -> tuple[float, float]:
|
||||
speed_weight = _sigmoid((TUCSON_4TH_GEN_CENTER_TAPER_SPEED_MAX - v_ego) / TUCSON_4TH_GEN_CENTER_TAPER_SPEED_WIDTH)
|
||||
center_weight = _sigmoid((TUCSON_4TH_GEN_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / TUCSON_4TH_GEN_CENTER_TAPER_LAT_WIDTH)
|
||||
return speed_weight, center_weight
|
||||
|
||||
|
||||
def get_tucson_4th_gen_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
|
||||
speed_weight, center_weight = _tucson_4th_gen_center_weights(desired_lateral_accel, v_ego)
|
||||
return 1.0 - (TUCSON_4TH_GEN_CENTER_TAPER_MAX * speed_weight * center_weight)
|
||||
|
||||
|
||||
def get_tucson_4th_gen_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0,
|
||||
desired_lateral_jerk: float = 0.0) -> float:
|
||||
del desired_lateral_jerk
|
||||
speed_weight, center_weight = _tucson_4th_gen_center_weights(desired_lateral_accel, v_ego)
|
||||
return get_hkg_canfd_base_friction_threshold(v_ego) * (1.0 + TUCSON_4TH_GEN_FRICTION_THRESHOLD_GAIN * speed_weight * center_weight)
|
||||
|
||||
|
||||
def _kia_forte_sigmoid(x: float) -> float:
|
||||
return _sigmoid(x)
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ A_CHANGE_COSTS = [200, 195, 180, 170] # Reverted to original 200 at low speeds
|
||||
LEAD_FILTER_TIME_LOW = 0.8 # Under 40 mph: Fast response for city emergency braking
|
||||
LEAD_FILTER_TIME_HIGH = 1.2 # Over 40 mph: Faster response to prevent highway gaps
|
||||
SPEED_FILTER_THRESHOLD = 40 * CV.MPH_TO_MS # 40 mph threshold
|
||||
DUPLICATE_VISION_LEAD_FILTER_TIME = 0.15
|
||||
|
||||
# DISTANCE ADAPTATION STRENGTH (How much penalties increase when close to lead)
|
||||
# [City, Urban Hwy, Rural Hwy, High Speed]
|
||||
@@ -382,6 +383,8 @@ class LongitudinalMpc:
|
||||
self.current_filter_time = LEAD_FILTER_TIME_LOW
|
||||
self.lead_a_filter = FirstOrderFilter(0.0, self.current_filter_time, self.dt)
|
||||
self.lead_v_filter = FirstOrderFilter(0.0, self.current_filter_time, self.dt)
|
||||
self.duplicate_lead_a_filters = [FirstOrderFilter(0.0, 0.0, self.dt, initialized=False) for _ in range(2)]
|
||||
self.duplicate_lead_v_filters = [FirstOrderFilter(0.0, 0.0, self.dt, initialized=False) for _ in range(2)]
|
||||
# Slew-limited filter factor to avoid abrupt 0.50↔1.00 jumps
|
||||
self.filter_time_factor = 1.0
|
||||
self.prev_filter_time_factor = 1.0
|
||||
@@ -423,6 +426,9 @@ class LongitudinalMpc:
|
||||
self.time_linearization = 0.0
|
||||
self.time_integrator = 0.0
|
||||
self.x0 = np.zeros(X_DIM)
|
||||
for lead_filter in (*self.duplicate_lead_a_filters, *self.duplicate_lead_v_filters):
|
||||
lead_filter.x = 0.0
|
||||
lead_filter.initialized = False
|
||||
self.set_weights()
|
||||
|
||||
def set_cost_weights(self, cost_weights, constraint_cost_weights):
|
||||
@@ -469,7 +475,6 @@ class LongitudinalMpc:
|
||||
self.lead_a_filter = FirstOrderFilter(current_a, self.current_filter_time, self.dt)
|
||||
self.lead_v_filter = FirstOrderFilter(current_v, self.current_filter_time, self.dt)
|
||||
self.prev_filter_time = self.current_filter_time
|
||||
|
||||
# Adaptive jerk factors for distance with interp scaling
|
||||
dist_factor = 1.0 + self.current_dist_adapt * (20.0 / max(lead_dist, 5.0))
|
||||
acceleration_jerk *= dist_factor
|
||||
@@ -477,7 +482,6 @@ class LongitudinalMpc:
|
||||
speed_jerk *= dist_factor
|
||||
|
||||
# Scene complexity adjustment based on model uncertainty
|
||||
prev_filter_time_factor = getattr(self, 'prev_filter_time_factor', 1.0)
|
||||
# Target factor from uncertainty
|
||||
if uncertainty <= 0.45:
|
||||
tgt_factor = 1.0
|
||||
@@ -496,10 +500,13 @@ class LongitudinalMpc:
|
||||
tgt_factor = max(tgt_factor, float(filter_time_factor_floor))
|
||||
|
||||
# Slew-limit changes to avoid step-wise filter jumps
|
||||
max_step = self.slew_per_sec * self.dt
|
||||
delta = np.clip(tgt_factor - self.filter_time_factor, -max_step, max_step)
|
||||
self.filter_time_factor += float(delta)
|
||||
filter_time_factor = float(self.filter_time_factor)
|
||||
if panic_bypass:
|
||||
# A real closing hazard must never wait for the comfort filter to unwind.
|
||||
self.filter_time_factor = 0.0
|
||||
else:
|
||||
max_step = self.slew_per_sec * self.dt
|
||||
delta = np.clip(tgt_factor - self.filter_time_factor, -max_step, max_step)
|
||||
self.filter_time_factor += float(delta)
|
||||
|
||||
# When uncertainty is moderately elevated, allow accel but cap jerk by increasing jerk cost
|
||||
if 0.45 <= uncertainty < 0.60:
|
||||
@@ -519,6 +526,7 @@ class LongitudinalMpc:
|
||||
self.set_cost_weights(cost_weights, constraint_cost_weights)
|
||||
|
||||
# Adjust filter time constants for complex scenes
|
||||
filter_time_factor = float(self.filter_time_factor)
|
||||
if abs(filter_time_factor - getattr(self, 'prev_filter_time_factor', 1.0)) > 0.05:
|
||||
new_filter_time = self.current_filter_time * filter_time_factor
|
||||
current_a = self.lead_a_filter.x if hasattr(self.lead_a_filter, 'x') else 0.0
|
||||
@@ -561,9 +569,11 @@ class LongitudinalMpc:
|
||||
lead_xv = np.column_stack((x_lead_traj, v_lead_traj))
|
||||
return lead_xv
|
||||
|
||||
def process_lead(self, lead, tracking_lead=True, t_follow=None):
|
||||
def process_lead(self, lead, tracking_lead=True, t_follow=None, *, lead_index=0,
|
||||
smooth_duplicate_vision=False):
|
||||
v_ego = self.x0[1]
|
||||
if lead is not None and lead.status and tracking_lead:
|
||||
lead_active = lead is not None and lead.status and tracking_lead
|
||||
if lead_active:
|
||||
x_lead = lead.dRel
|
||||
v_lead = lead.vLead
|
||||
a_lead = lead.aLeadK
|
||||
@@ -584,11 +594,30 @@ class LongitudinalMpc:
|
||||
x_lead = np.clip(x_lead, min_x_lead, 1e8)
|
||||
v_lead = np.clip(v_lead, 0.0, 1e8)
|
||||
a_lead = np.clip(a_lead, -10., 5.)
|
||||
# Apply smoothing filters with interp scaling
|
||||
self.lead_a_filter.update(a_lead)
|
||||
self.lead_v_filter.update(v_lead)
|
||||
a_lead = self.lead_a_filter.x
|
||||
v_lead = self.lead_v_filter.x
|
||||
if lead_active and smooth_duplicate_vision and not bool(getattr(lead, "radar", False)):
|
||||
# Keep the baseline filter synchronized so leaving this narrow comfort
|
||||
# path cannot introduce a state discontinuity.
|
||||
self.lead_a_filter.update(a_lead)
|
||||
self.lead_v_filter.update(v_lead)
|
||||
|
||||
filter_time = self.current_filter_time
|
||||
filter_time = max(filter_time, DUPLICATE_VISION_LEAD_FILTER_TIME)
|
||||
filter_time *= self.filter_time_factor
|
||||
|
||||
a_filter = self.duplicate_lead_a_filters[lead_index]
|
||||
v_filter = self.duplicate_lead_v_filters[lead_index]
|
||||
a_filter.update_alpha(filter_time)
|
||||
v_filter.update_alpha(filter_time)
|
||||
a_lead = a_filter.update(a_lead)
|
||||
v_lead = v_filter.update(v_lead)
|
||||
else:
|
||||
# Preserve the historical planner path outside the qualified comfort scene.
|
||||
self.lead_a_filter.update(a_lead)
|
||||
self.lead_v_filter.update(v_lead)
|
||||
a_lead = self.lead_a_filter.x
|
||||
v_lead = self.lead_v_filter.x
|
||||
self.duplicate_lead_a_filters[lead_index].initialized = False
|
||||
self.duplicate_lead_v_filters[lead_index].initialized = False
|
||||
lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau, v_ego)
|
||||
return lead_xv
|
||||
|
||||
@@ -806,13 +835,15 @@ class LongitudinalMpc:
|
||||
|
||||
def update(self, radarstate, v_cruise, x, v, a, j, danger_factor, t_follow,
|
||||
personality=log.LongitudinalPersonality.standard, tracking_lead=True,
|
||||
optional_far_lead_comfort=True):
|
||||
optional_far_lead_comfort=True, smooth_duplicate_vision=False):
|
||||
v_ego = self.x0[1]
|
||||
lead_one = radarstate.leadOne
|
||||
lead_two = radarstate.leadTwo
|
||||
self.status = tracking_lead and (lead_one.status or lead_two.status)
|
||||
lead_xv_0 = self.process_lead(lead_one, tracking_lead, t_follow=t_follow)
|
||||
lead_xv_1 = self.process_lead(lead_two, tracking_lead, t_follow=t_follow)
|
||||
lead_xv_0 = self.process_lead(lead_one, tracking_lead, t_follow=t_follow, lead_index=0,
|
||||
smooth_duplicate_vision=smooth_duplicate_vision)
|
||||
lead_xv_1 = self.process_lead(lead_two, tracking_lead, t_follow=t_follow, lead_index=1,
|
||||
smooth_duplicate_vision=smooth_duplicate_vision)
|
||||
|
||||
# To estimate a safe distance from a moving lead, we calculate how much stopping
|
||||
# distance that lead needs as a minimum. We can add that to the current distance
|
||||
|
||||
@@ -2755,7 +2755,8 @@ class LongitudinalPlanner:
|
||||
)
|
||||
# Duplicate vision tracks can share the same noisy velocity spike. Keep the
|
||||
# comfort path unless distance, TTC, or lead braking makes the scene urgent.
|
||||
if panic_bypass and self.is_nonurgent_duplicate_vision_follow(scene_v_ego, effective_t_follow):
|
||||
nonurgent_duplicate_vision_follow = self.is_nonurgent_duplicate_vision_follow(scene_v_ego, effective_t_follow)
|
||||
if panic_bypass and nonurgent_duplicate_vision_follow:
|
||||
panic_bypass = False
|
||||
|
||||
steady_follow_filter_floor = 0.0
|
||||
@@ -2820,7 +2821,8 @@ class LongitudinalPlanner:
|
||||
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j,
|
||||
sm['starpilotPlan'].dangerFactor, effective_t_follow,
|
||||
personality=personality, tracking_lead=lead_control_active,
|
||||
optional_far_lead_comfort=True)
|
||||
optional_far_lead_comfort=True,
|
||||
smooth_duplicate_vision=nonurgent_duplicate_vision_follow and not panic_bypass)
|
||||
|
||||
self.a_desired_trajectory_full = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
|
||||
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
|
||||
|
||||
@@ -76,6 +76,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import (
|
||||
get_kia_carnival_friction_center_fade_scale,
|
||||
get_kia_carnival_friction_threshold,
|
||||
get_tucson_4th_gen_center_taper_scale,
|
||||
get_tucson_4th_gen_friction_threshold,
|
||||
get_kia_ev6_center_taper_scale,
|
||||
get_kia_ev6_ff_scale,
|
||||
get_kia_ev6_friction_scale,
|
||||
@@ -839,6 +840,16 @@ class TestLatControl:
|
||||
assert low_speed_turn > 0.98
|
||||
assert high_speed_center > 0.98
|
||||
|
||||
def test_tucson_4th_gen_friction_threshold_targets_low_speed_center(self):
|
||||
base = get_hkg_canfd_base_friction_threshold(8.5)
|
||||
low_speed_center = get_tucson_4th_gen_friction_threshold(8.5, 0.0)
|
||||
low_speed_turn = get_tucson_4th_gen_friction_threshold(8.5, 0.50)
|
||||
high_speed_center = get_tucson_4th_gen_friction_threshold(20.0, 0.0)
|
||||
|
||||
assert low_speed_center == pytest.approx(base * 1.22, rel=0.01)
|
||||
assert low_speed_turn == pytest.approx(base, rel=0.01)
|
||||
assert high_speed_center == pytest.approx(get_hkg_canfd_base_friction_threshold(20.0), rel=0.01)
|
||||
|
||||
def test_tucson_4th_gen_default_update_path(self):
|
||||
controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.HYUNDAI_TUCSON_4TH_GEN)
|
||||
CS.vEgo = 8.5
|
||||
|
||||
@@ -14,7 +14,7 @@ import openpilot.selfdrive.controls.lib.longitudinal_planner as longitudinal_pla
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner, get_coast_accel, get_vehicle_min_accel, should_publish_planner_fcw
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import soften_far_radar_lead_accel, should_trigger_planner_fcw
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, soften_far_radar_lead_accel, should_trigger_planner_fcw
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
||||
|
||||
@@ -35,6 +35,71 @@ def make_lead(*, status: bool, d_rel: float = 200.0, v_lead: float = 0.0, a_lead
|
||||
return lead
|
||||
|
||||
|
||||
def test_mpc_duplicate_lead_filters_do_not_cross_contaminate_tracks():
|
||||
mpc = LongitudinalMpc()
|
||||
mpc.set_cur_state(20.0, 0.0)
|
||||
mpc.current_filter_time = 0.5
|
||||
lead_one = make_lead(status=True, d_rel=35.0, v_lead=12.0, model_prob=1.0)
|
||||
lead_two = make_lead(status=True, d_rel=35.0, v_lead=28.0, model_prob=1.0)
|
||||
|
||||
mpc.process_lead(lead_one, lead_index=0, smooth_duplicate_vision=True)
|
||||
mpc.process_lead(lead_two, lead_index=1, smooth_duplicate_vision=True)
|
||||
|
||||
assert mpc.duplicate_lead_v_filters[0].x == pytest.approx(12.0)
|
||||
assert mpc.duplicate_lead_v_filters[1].x == pytest.approx(28.0)
|
||||
|
||||
lead_one.vLead = 14.0
|
||||
mpc.process_lead(lead_one, lead_index=0, smooth_duplicate_vision=True)
|
||||
assert 12.0 < mpc.duplicate_lead_v_filters[0].x < 14.0
|
||||
assert mpc.duplicate_lead_v_filters[1].x == pytest.approx(28.0)
|
||||
|
||||
|
||||
def test_mpc_duplicate_vision_filter_damps_low_speed_velocity_noise():
|
||||
mpc = LongitudinalMpc()
|
||||
mpc.set_cur_state(18.0, 0.0)
|
||||
mpc.current_filter_time = 0.0
|
||||
lead = make_lead(status=True, d_rel=38.0, v_lead=17.0, model_prob=1.0)
|
||||
|
||||
mpc.process_lead(lead, lead_index=0, smooth_duplicate_vision=True)
|
||||
lead.vLead = 20.0
|
||||
mpc.process_lead(lead, lead_index=0, smooth_duplicate_vision=True)
|
||||
|
||||
assert 17.0 < mpc.duplicate_lead_v_filters[0].x < 20.0
|
||||
|
||||
|
||||
def test_mpc_distinct_vision_lead_uses_unchanged_baseline_filter():
|
||||
mpc = LongitudinalMpc()
|
||||
baseline_mpc = LongitudinalMpc()
|
||||
mpc.set_cur_state(18.0, 0.0)
|
||||
baseline_mpc.set_cur_state(18.0, 0.0)
|
||||
mpc.current_filter_time = 0.0
|
||||
baseline_mpc.current_filter_time = 0.0
|
||||
lead = make_lead(status=True, d_rel=38.0, v_lead=17.0, model_prob=1.0)
|
||||
|
||||
mpc.process_lead(lead, lead_index=0, smooth_duplicate_vision=False)
|
||||
baseline_mpc.process_lead(lead)
|
||||
lead.vLead = 20.0
|
||||
mpc.process_lead(lead, lead_index=0, smooth_duplicate_vision=False)
|
||||
baseline_mpc.process_lead(lead)
|
||||
|
||||
assert mpc.lead_v_filter.x == pytest.approx(baseline_mpc.lead_v_filter.x)
|
||||
|
||||
|
||||
def test_mpc_panic_bypass_immediately_removes_duplicate_vision_filter():
|
||||
mpc = LongitudinalMpc()
|
||||
mpc.set_cur_state(18.0, 0.0)
|
||||
mpc.current_filter_time = 0.0
|
||||
lead = make_lead(status=True, d_rel=25.0, v_lead=17.0, model_prob=1.0)
|
||||
|
||||
mpc.process_lead(lead, lead_index=0, smooth_duplicate_vision=True)
|
||||
mpc.set_weights(v_ego=18.0, panic_bypass=True)
|
||||
lead.vLead = 10.0
|
||||
mpc.process_lead(lead, lead_index=0, smooth_duplicate_vision=False)
|
||||
|
||||
assert mpc.filter_time_factor == 0.0
|
||||
assert mpc.lead_v_filter.x == pytest.approx(10.0)
|
||||
|
||||
|
||||
def make_model(v_ego: float, desired_accel: float, gas_press_prob: float = 1.0, brake_press_prob: float = 0.0):
|
||||
model = log.ModelDataV2.new_message()
|
||||
model.init('leadsV3', 3)
|
||||
|
||||
+28
-20
@@ -4,11 +4,12 @@ import {
|
||||
formatMetersToHuman,
|
||||
formatSecondsToHuman,
|
||||
getCoordinatesFromSearch,
|
||||
getMapboxSearchContext,
|
||||
getRoutes,
|
||||
removeRouteFromMap,
|
||||
getOrdinalSuffix,
|
||||
highlightRoute,
|
||||
} from "./navigation_utilities.js";
|
||||
} from "./navigation_utilities.js?v=nav-search-context-2";
|
||||
import { Modal } from "/assets/components/modal.js";
|
||||
|
||||
function sha1hex(str) {
|
||||
@@ -231,6 +232,7 @@ const state = reactive({
|
||||
fetched: false,
|
||||
initialized: false,
|
||||
isMetric: true,
|
||||
language: "",
|
||||
lastPosition: undefined,
|
||||
loadingRoute: false,
|
||||
mapboxPublic: undefined,
|
||||
@@ -249,6 +251,27 @@ const searchFieldState = reactive({ value: "" });
|
||||
|
||||
export function NavDestination() {
|
||||
|
||||
function getSearchContext(query) {
|
||||
const browserLanguages = typeof navigator === "undefined"
|
||||
? []
|
||||
: (navigator.languages || [navigator.language]);
|
||||
const context = getMapboxSearchContext(query, state.lastPosition, [state.language, ...browserLanguages]);
|
||||
if (state.lastPosition) {
|
||||
context.proximity = `${state.lastPosition.longitude},${state.lastPosition.latitude}`;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function getMapboxSuggestParams(query) {
|
||||
return new URLSearchParams({
|
||||
access_token: state.mapboxPublic,
|
||||
session_token: sessionToken,
|
||||
q: query,
|
||||
limit: 4,
|
||||
...getSearchContext(query),
|
||||
});
|
||||
}
|
||||
|
||||
function areRoutesEqual(a, b) {
|
||||
return a?.routeHash && b?.routeHash && a.routeHash === b.routeHash;
|
||||
}
|
||||
@@ -368,6 +391,7 @@ export function NavDestination() {
|
||||
state.amap1Key = data.amap1Key?.trim() || "";
|
||||
state.amap2Key = data.amap2Key?.trim() || "";
|
||||
state.isMetric = data.isMetric ?? true;
|
||||
state.language = data.language?.trim() || "";
|
||||
const hasMapbox = !!state.mapboxPublic && !!state.mapboxSecret;
|
||||
const hasAMap = !!state.amap1Key && !!state.amap2Key;
|
||||
state.missingKeys = !hasMapbox;
|
||||
@@ -422,15 +446,7 @@ export function NavDestination() {
|
||||
state.confirmedRoute = null;
|
||||
state.suggestions = "[]";
|
||||
if (state.searchProvider === "mapbox") {
|
||||
const params = new URLSearchParams({
|
||||
access_token: state.mapboxPublic,
|
||||
session_token: sessionToken,
|
||||
q: val,
|
||||
limit: 4
|
||||
});
|
||||
if (state.lastPosition) {
|
||||
params.set("proximity", `${state.lastPosition.longitude},${state.lastPosition.latitude}`);
|
||||
}
|
||||
const params = getMapboxSuggestParams(val);
|
||||
const res = await fetch(`https://api.mapbox.com/search/searchbox/v1/suggest?${params}`);
|
||||
const data = await res.json();
|
||||
state.suggestions = JSON.stringify(data.suggestions);
|
||||
@@ -589,15 +605,7 @@ export function NavDestination() {
|
||||
state.confirmedRoute = null;
|
||||
state.suggestions = "[]";
|
||||
if (state.searchProvider === "mapbox") {
|
||||
const params = new URLSearchParams({
|
||||
access_token: state.mapboxPublic,
|
||||
session_token: sessionToken,
|
||||
q: val,
|
||||
limit: 4
|
||||
});
|
||||
if (state.lastPosition) {
|
||||
params.set("proximity", `${state.lastPosition.longitude},${state.lastPosition.latitude}`);
|
||||
}
|
||||
const params = getMapboxSuggestParams(val);
|
||||
const res = await fetch(`https://api.mapbox.com/search/searchbox/v1/suggest?${params}`);
|
||||
const data = await res.json();
|
||||
state.suggestions = JSON.stringify(data.suggestions);
|
||||
@@ -637,7 +645,7 @@ export function NavDestination() {
|
||||
const retJson = await ret.json();
|
||||
coords = retJson.features[0].geometry.coordinates;
|
||||
} else {
|
||||
coords = await getCoordinatesFromSearch(label, state.mapboxPublic);
|
||||
coords = await getCoordinatesFromSearch(label, state.mapboxPublic, getSearchContext(label));
|
||||
}
|
||||
} else {
|
||||
coords = [sugg.location.lng, sugg.location.lat];
|
||||
|
||||
@@ -14,6 +14,38 @@ export function highlightRoute(map, routes, selectedRouteId) {
|
||||
});
|
||||
}
|
||||
|
||||
const JAPANESE_KANA_PATTERN = /[\u3040-\u30ff]/u;
|
||||
|
||||
function usesJapaneseLanguage(language) {
|
||||
return typeof language === 'string' && /^(?:main_)?ja(?:[-_]|$)/i.test(language.trim());
|
||||
}
|
||||
|
||||
function isLikelyInJapan(position) {
|
||||
if (!position) return false;
|
||||
|
||||
const latitude = Number(position.latitude);
|
||||
const longitude = Number(position.longitude);
|
||||
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return false;
|
||||
|
||||
// Main islands, Ryukyu Islands, and the Izu/Ogasawara island chains.
|
||||
const mainIslands = latitude >= 30 && latitude <= 45.8 && longitude >= 129.2 && longitude <= 146;
|
||||
const ryukyuIslands = latitude >= 24 && latitude < 30 && longitude >= 122.8 && longitude <= 131.5;
|
||||
const pacificIslands = latitude >= 20 && latitude < 30 && longitude >= 136 && longitude <= 154;
|
||||
return mainIslands || ryukyuIslands || pacificIslands;
|
||||
}
|
||||
|
||||
export function getMapboxSearchContext(query, position, preferredLanguages = []) {
|
||||
const languages = Array.isArray(preferredLanguages) ? preferredLanguages : [preferredLanguages];
|
||||
const japaneseSearch = JAPANESE_KANA_PATTERN.test(query || '') || languages.some(usesJapaneseLanguage);
|
||||
const hasPosition = Number.isFinite(Number(position?.latitude)) && Number.isFinite(Number(position?.longitude));
|
||||
|
||||
if (isLikelyInJapan(position) || (japaneseSearch && !hasPosition)) {
|
||||
return { language: 'ja', country: 'jp' };
|
||||
}
|
||||
if (japaneseSearch) return { language: 'ja' };
|
||||
return {};
|
||||
}
|
||||
|
||||
function addRouteSource(map, sourceId, feature) {
|
||||
if (map.getSource(sourceId)) {
|
||||
const layerId = `route-line-${sourceId.replace('route-', '')}`;
|
||||
@@ -141,11 +173,14 @@ export function addRouteToMap(map, routes, start, dest, onRouteSelect, useMetric
|
||||
map.fitBounds([start, dest], { padding, duration: 1000 });
|
||||
}
|
||||
|
||||
export async function getCoordinatesFromSearch(searchValue, mapboxPublic) {
|
||||
export async function getCoordinatesFromSearch(searchValue, mapboxPublic, searchContext = {}) {
|
||||
const params = new URLSearchParams({ access_token: mapboxPublic, q: searchValue });
|
||||
if (searchContext.language) params.set('language', searchContext.language);
|
||||
if (searchContext.country) params.set('country', searchContext.country);
|
||||
if (searchContext.proximity) params.set('proximity', searchContext.proximity);
|
||||
const response = await fetch(`https://api.mapbox.com/search/geocode/v6/forward?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
return data.features[0].geometry.coordinates;
|
||||
return data.features?.[0]?.geometry?.coordinates;
|
||||
}
|
||||
|
||||
export async function getRoutes(from, to, mapboxPublic) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { GalaxyPairing } from "/assets/components/tools/galaxy.js"
|
||||
import { Home } from "/assets/components/home/home.js"
|
||||
import { LongitudinalManeuvers } from "/assets/components/tools/longitudinal_maneuvers.js"
|
||||
import { MapsManager } from "/assets/components/tools/maps.js"
|
||||
import { NavDestination } from "/assets/components/navigation/navigation_destination.js?v=nav-search-context-1"
|
||||
import { NavDestination } from "/assets/components/navigation/navigation_destination.js?v=nav-search-context-2"
|
||||
import { NavKeys } from "/assets/components/navigation/navigation_keys.js?v=app-keys-session-1"
|
||||
import { RouteRecordings } from "/assets/components/recordings/dashcam_routes.js"
|
||||
import { SettingsView } from "/assets/components/settings.js"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<link rel="stylesheet" href="/assets/components/home/home.css">
|
||||
<link rel="stylesheet" href="/assets/components/main.css">
|
||||
<link rel="stylesheet" href="/assets/components/modal.css">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=nav-search-context-1">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=nav-search-context-2">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_keys.css?v=app-keys-session-1">
|
||||
<link rel="stylesheet" href="/assets/components/recordings/dashcam_routes.css">
|
||||
<link rel="stylesheet" href="/assets/components/recordings/screen_recordings.css">
|
||||
|
||||
@@ -4031,6 +4031,7 @@ def setup(app):
|
||||
"amap2Key": params.get("AMapKey2", encoding="utf8") or "",
|
||||
"destination": params.get("NavDestination", encoding="utf8") or "",
|
||||
"isMetric": params.get_bool("IsMetric"),
|
||||
"language": params.get("LanguageSetting", encoding="utf8") or "",
|
||||
"lastPosition": {
|
||||
"latitude": str(last_position.get("latitude", "")),
|
||||
"longitude": str(last_position.get("longitude", ""))
|
||||
|
||||
Reference in New Issue
Block a user