Skip to content

Commit 8fddb05

Browse files
committed
feat: Highlight selected feature in map-view
Refs: LIIK-784
1 parent 09a4d43 commit 8fddb05

File tree

5 files changed

+107
-23
lines changed

5 files changed

+107
-23
lines changed

map-view/src/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,12 @@ class App extends React.Component<AppProps, AppState> {
7171
<FeatureInfo
7272
features={features}
7373
mapConfig={mapConfig}
74-
onSelectFeature={(feature: Feature) => Map.showPlanOfRealDevice(feature, mapConfig)}
74+
onSelectFeatureShowPlan={(feature: Feature) => Map.showPlanOfRealDevice(feature, mapConfig)}
75+
onSelectFeatureHighLight={(feature: Feature) => Map.highlightFeature(feature, mapConfig)}
7576
onClose={() => {
7677
this.setState({ features: [] });
7778
Map.clearPlanOfRealVectorLayer();
79+
Map.clearHighlightLayer();
7880
}}
7981
/>
8082
)}

map-view/src/common/Map.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { FeatureLike } from "ol/Feature";
2424
import { Cluster } from "ol/source";
2525
import BaseObject from "ol/Object";
2626
import { getCenter } from "ol/extent";
27-
import { getSinglePointStyle, isCoordinateInsideFeature, isLayerClustered } from "./MapUtils";
27+
import { getHighlightStyle, getSinglePointStyle, isCoordinateInsideFeature, isLayerClustered } from "./MapUtils";
2828
import Static from "ol/source/ImageStatic";
2929

3030
class Map {
@@ -70,6 +70,11 @@ class Map {
7070
*/
7171
private planOfRealVectorLayer: VectorLayer<VectorSource>;
7272

73+
/**
74+
* A layer to draw highlighted feature (when active in FeatureInfo dialog)
75+
*/
76+
private highLightedFeatureLayer: VectorLayer<VectorSource>;
77+
7378
/**
7479
* Callback function to process features returned from GetFeatureInfo requests
7580
*
@@ -90,6 +95,7 @@ class Map {
9095
const nonClusteredOverlayLayerGroup = this.createNonClusteredOverlayLayerGroup(mapConfig);
9196
this.planRealDiffVectorLayer = Map.createPlanRealDiffVectorLayer();
9297
this.planOfRealVectorLayer = Map.createPlanOfRealVectorLayer();
98+
this.highLightedFeatureLayer = Map.createHighLightLayer();
9399

94100
const resolutions = [256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5, 0.25, 0.125, 0.0625];
95101
const projection = this.getProjection();
@@ -109,11 +115,12 @@ class Map {
109115
nonClusteredOverlayLayerGroup,
110116
this.planRealDiffVectorLayer,
111117
this.planOfRealVectorLayer,
118+
this.highLightedFeatureLayer,
112119
],
113120
controls: this.getControls(),
114121
view,
115122
});
116-
123+
this.highLightedFeatureLayer.setVisible(true);
117124
this.overViewMap = new OverviewMap({
118125
className: "ol-overviewmap",
119126
layers: [
@@ -266,6 +273,21 @@ class Map {
266273
});
267274
}
268275

276+
/**
277+
*
278+
* @param feature Target feature to be highlighted
279+
*/
280+
highlightFeature(feature: Feature, mapConfig: MapConfig) {
281+
this.clearHighlightLayer();
282+
const olFeature = new OlFeature({
283+
geometry: feature.getProperties().geometry,
284+
name: "HiglightedFeature",
285+
});
286+
287+
this.highLightedFeatureLayer.setStyle(getHighlightStyle(feature, mapConfig));
288+
this.highLightedFeatureLayer.getSource()?.addFeature(olFeature);
289+
}
290+
269291
showAllPlanAndRealDifferences(realLayer: VectorLayer<VectorSource>, planLayer: VectorLayer<VectorSource>) {
270292
let realFeatures: FeatureLike[],
271293
planFeatures: FeatureLike[] = [];
@@ -322,10 +344,17 @@ class Map {
322344
}
323345
}
324346

347+
private static createHighLightLayer() {
348+
const highLightLayerSource = new VectorSource({});
349+
return new VectorLayer({
350+
source: highLightLayerSource,
351+
});
352+
}
353+
325354
private static createPlanRealDiffVectorLayer() {
326-
const planRealDiffVectorLayer = new VectorSource({});
355+
const planRealDiffVectorLayerSource = new VectorSource({});
327356
return new VectorLayer({
328-
source: planRealDiffVectorLayer,
357+
source: planRealDiffVectorLayerSource,
329358
// Point style
330359
style: new Style({
331360
image: new Circle({
@@ -348,9 +377,9 @@ class Map {
348377
}
349378

350379
private static createPlanOfRealVectorLayer() {
351-
const planOfRealVectorLayer = new VectorSource({});
380+
const planOfRealVectorLayerSource = new VectorSource({});
352381
return new VectorLayer({
353-
source: planOfRealVectorLayer,
382+
source: planOfRealVectorLayerSource,
354383
// Point style
355384
style: new Style({
356385
image: new Circle({
@@ -406,6 +435,10 @@ class Map {
406435
this.planOfRealVectorLayer.getSource()!.clear();
407436
}
408437

438+
clearHighlightLayer() {
439+
this.highLightedFeatureLayer.getSource()!.clear();
440+
}
441+
409442
applyProjectFilters(overlayConfig: LayerConfig, projectId: string) {
410443
const { sourceUrl } = overlayConfig;
411444
const filter_field = "responsible_entity_name";

map-view/src/common/MapUtils.ts

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Fill, Icon, Stroke, Style, Circle as CircleStyle } from "ol/style";
33
import { FeatureLike } from "ol/Feature";
44
import RenderFeature from "ol/render/Feature";
55
import { Coordinate } from "ol/coordinate";
6-
import { Feature, LayerConfig } from "../models";
6+
import { Feature, LayerConfig, MapConfig } from "../models";
77
import VectorLayer from "ol/layer/Vector";
88
import { Cluster } from "ol/source";
99

@@ -73,6 +73,49 @@ const geometryStyles = {
7373
}),
7474
};
7575

76+
const highLightStyle = new Style({
77+
fill: new Fill({ color: "yellow" }),
78+
image: new CircleStyle({
79+
radius: 5,
80+
fill: new Fill({ color: "yellow" }),
81+
stroke: defaultStroke,
82+
}),
83+
stroke: new Stroke({
84+
color: "yellow",
85+
width: 2,
86+
}),
87+
});
88+
89+
function getIconStyle(
90+
iconsUrl: string,
91+
iconType: string,
92+
iconScale: number,
93+
deviceTypeCode: string,
94+
deviceTypeIcon: string,
95+
) {
96+
return new Style({
97+
image: new Icon({
98+
src: getIconSrc(iconsUrl, iconType, deviceTypeCode, deviceTypeIcon),
99+
scale: iconScale,
100+
}),
101+
});
102+
}
103+
104+
export function getHighlightStyle(feature: Feature, mapConfig: MapConfig) {
105+
const { traffic_sign_icons_url, icon_type, icon_scale, overlayConfig } = mapConfig;
106+
const dtCode = feature.getProperties().device_type_code;
107+
if (trafficSignIconsEnabled(feature, overlayConfig) && dtCode) {
108+
return getIconStyle(
109+
traffic_sign_icons_url,
110+
icon_type,
111+
icon_scale,
112+
dtCode,
113+
feature.getProperties().device_type_icon,
114+
);
115+
}
116+
return highLightStyle;
117+
}
118+
76119
export function getSinglePointStyle(
77120
feature: FeatureLike,
78121
use_traffic_sign_icons: boolean,
@@ -82,17 +125,13 @@ export function getSinglePointStyle(
82125
) {
83126
if (use_traffic_sign_icons && feature.get("device_type_code") !== null) {
84127
// Traffic sign style
85-
return new Style({
86-
image: new Icon({
87-
src: getIconSrc(
88-
traffic_sign_icons_url,
89-
icon_type,
90-
feature.get("device_type_code"),
91-
feature.get("device_type_icon"),
92-
),
93-
scale: icon_scale,
94-
}),
95-
});
128+
return getIconStyle(
129+
traffic_sign_icons_url,
130+
icon_type,
131+
icon_scale,
132+
feature.get("device_type_code"),
133+
feature.get("device_type_icon"),
134+
);
96135
}
97136

98137
const geometry = feature.getGeometry();
@@ -194,3 +233,8 @@ function getFeatureLayer(featureType: string, overlayConfig: LayerConfig) {
194233
function getFeatureType(feature: Feature) {
195234
return feature["id_"].split(".")[0];
196235
}
236+
237+
function trafficSignIconsEnabled(feature: Feature, overlayConfig: LayerConfig) {
238+
const layer = getFeatureLayer(getFeatureType(feature), overlayConfig);
239+
return layer?.use_traffic_sign_icons;
240+
}

map-view/src/components/FeatureInfo.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ test("renders feature info", () => {
2929
features={mockFeatures}
3030
mapConfig={mockMapConfig}
3131
onClose={() => {}}
32-
onSelectFeature={(feature = mockFeatures[0]) => new Promise((resolve) => resolve(0))}
32+
onSelectFeatureShowPlan={(feature = mockFeatures[0]) => new Promise((resolve) => resolve(0))}
33+
onSelectFeatureHighLight={(feature = mockFeatures[0]) => undefined}
3334
/>,
3435
);
3536
const featureInfoTitle = getByText("Overlay 1");

map-view/src/components/FeatureInfo.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const styles = (theme: Theme) =>
3939
interface FeatureInfoProps extends WithStyles<typeof styles>, WithTranslation {
4040
features: Feature[];
4141
mapConfig: MapConfig;
42-
onSelectFeature: (feature: Feature) => Promise<any>;
42+
onSelectFeatureShowPlan: (feature: Feature) => Promise<any>;
43+
onSelectFeatureHighLight: (feature: Feature) => void;
4344
onClose: () => void;
4445
}
4546

@@ -87,9 +88,12 @@ class FeatureInfo extends React.Component<FeatureInfoProps, FeatureInfoState> {
8788
}
8889

8990
runOnSelectFeature(featureIndex: number) {
90-
const { features, onSelectFeature } = this.props;
91+
const { features, onSelectFeatureShowPlan, onSelectFeatureHighLight } = this.props;
9192
const feature = features[featureIndex];
92-
onSelectFeature(feature).then((distance: number) => this.setState({ realPlanDistance: distance }));
93+
onSelectFeatureShowPlan(feature).then(
94+
(distance: number) => distance && this.setState({ realPlanDistance: distance }),
95+
);
96+
onSelectFeatureHighLight(feature);
9397
}
9498

9599
setFeatureIndex(featureIndex: number) {

0 commit comments

Comments
 (0)