|
| 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} |
0 commit comments