fix: Add route-station link area

This commit is contained in:
2025-06-16 12:26:19 +03:00
parent 32a7cb44d1
commit d415441af8
8 changed files with 570 additions and 119 deletions

View File

@ -172,7 +172,7 @@ class MapStore {
} else if (featureType === "route") {
data = {
route_number: properties.name,
path: geometry.coordinates,
path: geometry.coordinates.map((coord: any) => [coord[1], coord[0]]), // Swap coordinates
};
} else if (featureType === "sight") {
data = {
@ -210,8 +210,8 @@ class MapStore {
{
...oldData,
path: data.path,
center_latitude: data.path[0][1],
center_longitude: data.path[0][0],
center_latitude: data.path[0][0], // First coordinate is latitude
center_longitude: data.path[0][1], // Second coordinate is longitude
}
);
}
@ -485,65 +485,38 @@ class MapService {
const isHovered = this.hoveredFeatureId === fId;
const isLassoSelected = fId !== undefined && this.selectedIds.has(fId);
if (geometryType === "Point") {
const defaultPointStyle =
featureType === "sight" ? this.sightIconStyle : this.busIconStyle;
const selectedPointStyle =
featureType === "sight"
? this.selectedSightIconStyle
: this.selectedBusIconStyle;
if (isEditSelected) {
return selectedPointStyle;
if (isHovered) {
if (geometryType === "Point") {
return featureType === "sight"
? this.hoverSightIconStyle
: this.universalHoverStylePoint;
}
if (isHovered) {
// Only apply hover styles if not in edit mode
if (this.mode !== "edit") {
return featureType === "sight"
? this.hoverSightIconStyle
: this.universalHoverStylePoint;
}
return defaultPointStyle;
}
if (isLassoSelected) {
let imageStyle;
if (featureType === "sight") {
imageStyle = new RegularShape({
fill: new Fill({ color: "#14b8a6" }),
stroke: new Stroke({ color: "#fff", width: 2 }),
points: 5,
radius: 12,
radius2: 6,
angle: 0,
});
} else {
imageStyle = new CircleStyle({
radius: 10,
fill: new Fill({ color: "#14b8a6" }),
stroke: new Stroke({ color: "#fff", width: 2 }),
});
}
return new Style({ image: imageStyle, zIndex: Infinity });
}
return defaultPointStyle;
} else if (geometryType === "LineString") {
if (isEditSelected) {
return this.selectedStyle;
}
if (isHovered) {
return this.universalHoverStyleLine;
}
if (isLassoSelected) {
return new Style({
stroke: new Stroke({ color: "#14b8a6", width: 6 }),
zIndex: Infinity,
});
}
return this.defaultStyle;
return this.universalHoverStyleLine;
}
if (isLassoSelected) {
if (geometryType === "Point") {
return featureType === "sight"
? this.selectedSightIconStyle
: this.selectedBusIconStyle;
}
return this.selectedStyle;
}
if (isEditSelected) {
if (geometryType === "Point") {
return featureType === "sight"
? this.selectedSightIconStyle
: this.selectedBusIconStyle;
}
return this.selectedStyle;
}
if (geometryType === "Point") {
return featureType === "sight"
? this.sightIconStyle
: this.busIconStyle;
}
return this.defaultStyle;
},
});
@ -763,7 +736,9 @@ class MapService {
if (!route.path || route.path.length === 0) return;
const coordinates = route.path
.filter((c) => c[0] != null && c[1] != null)
.map((c) => transform(c, "EPSG:4326", projection));
.map((c: [number, number]) =>
transform([c[1], c[0]], "EPSG:4326", projection)
); // Swap coordinates
if (coordinates.length === 0) return;
const line = new LineString(coordinates);
const feature = new Feature({ geometry: line, name: route.route_number });
@ -866,6 +841,11 @@ class MapService {
this.redo();
return;
}
if ((event.ctrlKey || event.metaKey) && event.key === "r") {
event.preventDefault();
this.unselect();
return;
}
if (event.key === "Escape") {
this.unselect();
}
@ -1090,29 +1070,29 @@ class MapService {
);
if (!featureAtPixel) {
if (ctrlKey) {
// При ctrl + клик вне сущности сбрасываем выбор
this.setSelectedIds(new Set());
}
if (ctrlKey) this.unselect();
return;
}
const featureId = featureAtPixel.getId();
if (featureId === undefined) return;
const newSet = new Set(this.selectedIds);
if (ctrlKey) {
// При ctrl + клик на сущность добавляем/удаляем её из выбора
const newSet = new Set(this.selectedIds);
// Toggle selection for the clicked feature
if (newSet.has(featureId)) {
newSet.delete(featureId);
} else {
newSet.add(featureId);
}
this.setSelectedIds(newSet);
} else {
// При обычном клике на сущность выбираем только её
this.setSelectedIds(new Set([featureId]));
// Single selection
newSet.clear();
newSet.add(featureId);
}
this.setSelectedIds(newSet);
}
public selectFeature(featureId: string | number | undefined): void {
@ -1127,14 +1107,6 @@ class MapService {
return;
}
if (this.mode === "edit") {
this.selectInteraction.getFeatures().clear();
this.selectInteraction.getFeatures().push(feature);
// @ts-ignore
const selectEvent = new SelectEvent("select", [feature], []);
this.selectInteraction.dispatchEvent(selectEvent);
}
this.setSelectedIds(new Set([featureId]));
const view = this.map.getView();
@ -1286,7 +1258,30 @@ class MapService {
public setSelectedIds(ids: Set<string | number>) {
this.selectedIds = new Set(ids);
if (this.onSelectionChange) this.onSelectionChange(this.selectedIds);
this.vectorLayer.changed();
// Update selectInteraction to match selectedIds
if (this.selectInteraction) {
this.selectInteraction.getFeatures().clear();
ids.forEach((id) => {
const feature = this.vectorSource.getFeatureById(id);
if (feature) {
this.selectInteraction.getFeatures().push(feature);
}
});
}
// Update modifyInteraction
this.modifyInteraction.setActive(ids.size > 0);
// Update feature selection in sidebar
if (ids.size === 1) {
const feature = this.vectorSource.getFeatureById(Array.from(ids)[0]);
if (feature) {
this.onFeatureSelect(feature);
}
} else {
this.onFeatureSelect(null);
}
}
public getSelectedIds() {
@ -1501,8 +1496,10 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
}, [mapFeatures, searchQuery]);
const handleFeatureClick = useCallback(
// @ts-ignore
(id) => mapService?.selectFeature(id),
(id: string | number | undefined) => {
if (!id || !mapService) return;
mapService.selectFeature(id);
},
[mapService]
);
@ -1521,7 +1518,7 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
const handleCheckboxChange = useCallback(
(id: string | number | undefined) => {
if (id === undefined) return;
if (!id || !mapService) return;
const newSet = new Set(selectedIds);
if (newSet.has(id)) {
newSet.delete(id);
@ -1529,11 +1526,9 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
newSet.add(id);
}
setSelectedIds(newSet);
if (mapService) {
mapService.setSelectedIds(newSet);
}
mapService.setSelectedIds(newSet);
},
[selectedIds, setSelectedIds, mapService]
[mapService, selectedIds, setSelectedIds]
);
const handleBulkDelete = useCallback(() => {
@ -1630,7 +1625,7 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
<input
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer"
checked={!!isChecked}
checked={isChecked}
onChange={() => handleCheckboxChange(sId)}
onClick={(e) => e.stopPropagation()}
aria-label={`Выбрать ${sName}`}
@ -1719,7 +1714,7 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
<input
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer"
checked={!!isChecked}
checked={isChecked}
onChange={() => handleCheckboxChange(lId)}
onClick={(e) => e.stopPropagation()}
aria-label={`Выбрать ${lName}`}
@ -1808,7 +1803,7 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
<input
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer"
checked={!!isChecked}
checked={isChecked}
onChange={() => handleCheckboxChange(sId)}
onClick={(e) => e.stopPropagation()}
aria-label={`Выбрать ${sName}`}
@ -2008,11 +2003,13 @@ export const MapPage: React.FC = () => {
);
const handleMapClick = useCallback(
(event: MapBrowserEvent<any>) => {
if (!mapServiceInstance) return;
mapServiceInstance.handleMapClick(event, event.originalEvent.ctrlKey);
(event: any) => {
if (!mapServiceInstance || isLassoActive) return;
const ctrlKey =
event.originalEvent.ctrlKey || event.originalEvent.metaKey;
mapServiceInstance.handleMapClick(event, ctrlKey);
},
[mapServiceInstance]
[mapServiceInstance, isLassoActive]
);
useEffect(() => {
@ -2211,6 +2208,12 @@ export const MapPage: React.FC = () => {
</span>{" "}
- Повторить действие
</li>
<li>
<span className="font-mono bg-gray-100 px-1 rounded">
Ctrl+R
</span>{" "}
- Отменить выделение
</li>
</ul>
<button
onClick={() => setShowHelp(false)}