feat(map): control ghost pin movement speed
This commit is contained in:
@@ -59,6 +59,27 @@ export function GhostPinSettingsPanel({
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Максимальная скорость"
|
||||
value={configuration.maximumSpeedMetersPerSecond}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} м/с`}
|
||||
onChange={(maximumSpeedMetersPerSecond) => updateDraft((current) => {
|
||||
current.view.ghostPins.maximumSpeedMetersPerSecond =
|
||||
maximumSpeedMetersPerSecond;
|
||||
current.view.ghostPins.positions =
|
||||
current.view.ghostPins.positions.map((pin) => ({
|
||||
...pin,
|
||||
speedMetersPerSecond: Math.min(
|
||||
pin.speedMetersPerSecond,
|
||||
maximumSpeedMetersPerSecond,
|
||||
),
|
||||
}));
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Симуляция движения"
|
||||
checked={configuration.simulationEnabled}
|
||||
|
||||
@@ -9,17 +9,23 @@ export function generateGhostPins({
|
||||
anchor,
|
||||
count,
|
||||
radiusMeters,
|
||||
maximumSpeedMetersPerSecond,
|
||||
random = Math.random,
|
||||
timestamp = Date.now(),
|
||||
}: {
|
||||
anchor: MapGeoPoint;
|
||||
count: number;
|
||||
radiusMeters: number;
|
||||
maximumSpeedMetersPerSecond: number;
|
||||
random?: () => number;
|
||||
timestamp?: number;
|
||||
}): MapGhostPin[] {
|
||||
const safeCount = Math.min(100, Math.max(1, Math.round(count)));
|
||||
const safeRadius = Math.min(100_000, Math.max(100, radiusMeters));
|
||||
const safeMaximumSpeed = Math.min(
|
||||
100,
|
||||
Math.max(0.1, maximumSpeedMetersPerSecond),
|
||||
);
|
||||
const labels = new Set<string>();
|
||||
return Array.from({ length: safeCount }, (_, index) => {
|
||||
const distance = safeRadius * Math.sqrt(clampUnit(random()));
|
||||
@@ -36,7 +42,8 @@ export function generateGhostPins({
|
||||
label,
|
||||
...position,
|
||||
headingDegrees: clampUnit(random()) * 360,
|
||||
speedMetersPerSecond: 3 + clampUnit(random()) * 15,
|
||||
speedMetersPerSecond:
|
||||
safeMaximumSpeed * (0.2 + clampUnit(random()) * 0.8),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface MapGhostPinPresentation {
|
||||
export interface MapGhostPinConfiguration {
|
||||
count: number;
|
||||
radiusMeters: number;
|
||||
maximumSpeedMetersPerSecond: number;
|
||||
simulationEnabled: boolean;
|
||||
anchor: MapGeoPoint | null;
|
||||
positions: MapGhostPin[];
|
||||
@@ -226,6 +227,7 @@ export function defaultGhostPinConfiguration(): MapGhostPinConfiguration {
|
||||
return {
|
||||
count: 12,
|
||||
radiusMeters: 5_000,
|
||||
maximumSpeedMetersPerSecond: 18,
|
||||
simulationEnabled: false,
|
||||
anchor: null,
|
||||
positions: [],
|
||||
@@ -341,6 +343,8 @@ export function encodeMapViewPut(document: MapViewDocument): unknown {
|
||||
ghost_pins: {
|
||||
count: document.view.ghostPins.count,
|
||||
radius_meters: document.view.ghostPins.radiusMeters,
|
||||
maximum_speed_meters_per_second:
|
||||
document.view.ghostPins.maximumSpeedMetersPerSecond,
|
||||
simulation_enabled: document.view.ghostPins.simulationEnabled,
|
||||
anchor: document.view.ghostPins.anchor
|
||||
? {
|
||||
@@ -466,6 +470,7 @@ function decodeGhostPinConfiguration(value: unknown): MapGhostPinConfiguration {
|
||||
[
|
||||
"count",
|
||||
"radius_meters",
|
||||
"maximum_speed_meters_per_second",
|
||||
"simulation_enabled",
|
||||
"anchor",
|
||||
"positions",
|
||||
@@ -483,6 +488,23 @@ function decodeGhostPinConfiguration(value: unknown): MapGhostPinConfiguration {
|
||||
if (positions.length > 100) {
|
||||
throw new Error("ghost_pins.positions превышает лимит.");
|
||||
}
|
||||
const maximumSpeedMetersPerSecond = requireNumber(
|
||||
configuration.maximum_speed_meters_per_second,
|
||||
"ghost_pins.maximum_speed_meters_per_second",
|
||||
0.1,
|
||||
100,
|
||||
);
|
||||
const decodedPositions = positions.map((pin, index) => decodeGhostPin(
|
||||
pin,
|
||||
`ghost_pins.positions[${index}]`,
|
||||
));
|
||||
if (
|
||||
decodedPositions.some(
|
||||
(pin) => pin.speedMetersPerSecond > maximumSpeedMetersPerSecond,
|
||||
)
|
||||
) {
|
||||
throw new Error("Скорость Ghost Pin превышает настроенный максимум.");
|
||||
}
|
||||
return {
|
||||
count: requireInteger(configuration.count, "ghost_pins.count", 1, 100),
|
||||
radiusMeters: requireNumber(
|
||||
@@ -491,15 +513,13 @@ function decodeGhostPinConfiguration(value: unknown): MapGhostPinConfiguration {
|
||||
100,
|
||||
100_000,
|
||||
),
|
||||
maximumSpeedMetersPerSecond,
|
||||
simulationEnabled: requireBoolean(
|
||||
configuration.simulation_enabled,
|
||||
"ghost_pins.simulation_enabled",
|
||||
),
|
||||
anchor,
|
||||
positions: positions.map((pin, index) => decodeGhostPin(
|
||||
pin,
|
||||
`ghost_pins.positions[${index}]`,
|
||||
)),
|
||||
positions: decodedPositions,
|
||||
presentation: decodeGhostPinPresentation(configuration.presentation),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,6 +119,8 @@ export function WorldMapWorkspace({
|
||||
anchor,
|
||||
count: current.view.ghostPins.count,
|
||||
radiusMeters: current.view.ghostPins.radiusMeters,
|
||||
maximumSpeedMetersPerSecond:
|
||||
current.view.ghostPins.maximumSpeedMetersPerSecond,
|
||||
});
|
||||
current.view.layerVisibility.targets = true;
|
||||
return current;
|
||||
|
||||
Reference in New Issue
Block a user