Skip to content

Commit e983f57

Browse files
committed
Add first privacy attack alg
1 parent e4d2fb3 commit e983f57

File tree

9 files changed

+528
-154
lines changed

9 files changed

+528
-154
lines changed

icons/skmob.png

11.1 KB
Loading

qgis_processing/createTrajectoriesAlgorithm.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import sys
22

3-
from qgis.PyQt.QtCore import QCoreApplication
4-
53
sys.path.append("..")
64

75
from .trajectoriesAlgorithm import TrajectoryManipulationAlgorithm

qgis_processing/icons/skmob.png

11.1 KB
Loading
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import os
2+
import sys
3+
4+
from pandas import merge
5+
from pyproj import CRS
6+
from qgis.PyQt.QtGui import QIcon
7+
from qgis.PyQt.QtCore import QCoreApplication, QVariant
8+
from qgis.core import QgsWkbTypes, QgsField, QgsProcessingUtils
9+
10+
try:
11+
from skmob.privacy import attacks
12+
from skmob.core.trajectorydataframe import TrajDataFrame
13+
except ImportError as error:
14+
raise ImportError(
15+
"Missing optional dependencies. To use the privacy attacks please "
16+
"install scikit-mobility "
17+
"(see https://github.com/scikit-mobility/scikit-mobility)."
18+
) from error
19+
20+
sys.path.append("..")
21+
22+
from .qgisUtils import tc_from_df
23+
from .trajectoriesAlgorithm import TrajectoryManipulationAlgorithm
24+
25+
pluginPath = os.path.dirname(__file__)
26+
27+
28+
class HomeWorkAttack(TrajectoryManipulationAlgorithm):
29+
def __init__(self):
30+
super().__init__()
31+
32+
def icon(self):
33+
return QIcon(os.path.join(pluginPath, "icons", "skmob.png"))
34+
35+
def name(self):
36+
return "home_work_attack"
37+
38+
def displayName(self):
39+
return self.tr("Home and work attack")
40+
41+
def group(self):
42+
return self.tr("Privacy")
43+
44+
def groupId(self):
45+
return "TrajectoryPrivacy"
46+
47+
def shortHelpString(self):
48+
return self.tr(
49+
"<p>In a home and work attack the adversary knows the "
50+
"coordinates of the two locations most frequently visited "
51+
"by an individual, and matches them against frequency "
52+
"vectors. A frequency vector is an aggregation on trajectory "
53+
"data showing the unique locations visited by an individual "
54+
"and the frequency with which he visited those locations. "
55+
)
56+
57+
def helpUrl(self):
58+
return "https://scikit-mobility.github.io/scikit-mobility/reference/privacy.html#skmob.privacy.attacks.HomeWorkAttack"
59+
60+
def createInstance(self):
61+
return type(self)()
62+
63+
def processAlgorithm(self, parameters, context, feedback):
64+
df = self.create_df(parameters, context)
65+
df_copy = df.drop(
66+
columns=["lng", "lat"]
67+
) # skmob may throw an error if these columns exist
68+
tdf = TrajDataFrame(
69+
df_copy,
70+
longitude="geom_x",
71+
latitude="geom_y",
72+
datetime=self.timestamp_field,
73+
user_id=self.traj_id_field,
74+
)
75+
at = attacks.HomeWorkAttack()
76+
r = at.assess_risk(tdf)
77+
df = merge(r, df, on="uid")
78+
79+
crs = self.input_layer.sourceCrs()
80+
crs_no = CRS(int(crs.geographicCrsAuthId().split(":")[1]))
81+
tc = tc_from_df(df, self.timestamp_field, self.traj_id_field, crs_no)
82+
tc.add_speed(units=tuple(self.speed_units), overwrite=True)
83+
tc.add_direction(overwrite=True)
84+
85+
self.fields_pts = self.get_pt_fields(
86+
[
87+
QgsField(tc.get_speed_col(), QVariant.Double),
88+
QgsField(tc.get_direction_col(), QVariant.Double),
89+
QgsField("risk", QVariant.Double),
90+
],
91+
)
92+
(self.sink_pts, self.dest_pts) = self.parameterAsSink(
93+
parameters,
94+
self.OUTPUT_PTS,
95+
context,
96+
self.fields_pts,
97+
QgsWkbTypes.Point,
98+
crs,
99+
)
100+
101+
self.fields_trajs = self.get_traj_fields([QgsField("risk", QVariant.Double)])
102+
(self.sink_trajs, self.dest_trajs) = self.parameterAsSink(
103+
parameters,
104+
self.OUTPUT_TRAJS,
105+
context,
106+
self.fields_trajs,
107+
QgsWkbTypes.LineStringM,
108+
crs,
109+
)
110+
111+
self.processTc(tc)
112+
113+
return {self.OUTPUT_PTS: self.dest_pts, self.OUTPUT_TRAJS: self.dest_trajs}
114+
115+
def processTc(self, tc):
116+
self.tc_to_sink(tc)
117+
for traj in tc.trajectories:
118+
self.traj_to_sink(traj, attr_mean_to_add=["risk"])
119+
120+
def postProcessAlgorithm(self, context, feedback):
121+
pts_layer = QgsProcessingUtils.mapLayerFromString(self.dest_pts, context)
122+
pts_layer.loadNamedStyle(os.path.join(pluginPath, "styles", "pts.qml"))
123+
traj_layer = QgsProcessingUtils.mapLayerFromString(self.dest_trajs, context)
124+
traj_layer.loadNamedStyle(os.path.join(pluginPath, "styles", "risk.qml"))
125+
return {self.OUTPUT_PTS: self.dest_pts, self.OUTPUT_TRAJS: self.dest_trajs}

qgis_processing/qgisUtils.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
QgsGeometry,
88
QgsPointXY,
99
QgsFeatureSink,
10-
QgsMessageLog,
11-
Qgis
10+
QgsMessageLog,
11+
Qgis,
1212
)
1313
from qgis.PyQt.QtCore import QDateTime
1414

@@ -20,25 +20,34 @@ def trajectories_from_qgis_point_layer(
2020
return tc_from_pt_layer(layer, time_field_name, trajectory_id_field, time_format)
2121

2222

23-
def tc_from_pt_layer(layer, time_field_name, trajectory_id_field):
23+
def df_from_pt_layer(layer, time_field_name, trajectory_id_field):
2424
names = [field.name() for field in layer.fields()]
2525
data = []
2626
for feature in layer.getFeatures():
2727
my_dict = {}
2828
for i, a in enumerate(feature.attributes()):
29-
#QgsMessageLog.logMessage(f"{names[i]} | {time_field_name}", "Trajectools", level=Qgis.Info )
29+
# QgsMessageLog.logMessage(f"{names[i]} | {time_field_name}", "Trajectools", level=Qgis.Info )
3030
if names[i] == time_field_name: # and type(a) == "QDateTime":
3131
try:
3232
a = a.toPyDateTime()
3333
except:
34-
pass
34+
pass
3535
my_dict[names[i]] = a
3636
pt = feature.geometry().asPoint()
3737
my_dict["geom_x"] = pt.x()
3838
my_dict["geom_y"] = pt.y()
3939
data.append(my_dict)
4040
df = pd.DataFrame(data)
41+
return df
42+
43+
44+
def tc_from_pt_layer(layer, time_field_name, trajectory_id_field):
45+
df = df_from_pt_layer(layer, time_field_name, trajectory_id_field)
4146
crs = CRS(int(layer.sourceCrs().geographicCrsAuthId().split(":")[1]))
47+
return tc_from_df(df, time_field_name, trajectory_id_field, crs)
48+
49+
50+
def tc_from_df(df, time_field_name, trajectory_id_field, crs):
4251
tc = TrajectoryCollection(
4352
df,
4453
traj_id_col=trajectory_id_field,
@@ -54,5 +63,7 @@ def feature_from_gdf_row(row):
5463
f = QgsFeature()
5564
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(row.geometry.x, row.geometry.y)))
5665
values = row.values.tolist()[:-1]
66+
# for v in values:
67+
# QgsMessageLog.logMessage(str(type(v)), "Trajectools", level=Qgis.Info )
5768
f.setAttributes(values)
5869
return f

0 commit comments

Comments
 (0)