diff --git a/prog/gtfs.py b/prog/gtfs.py new file mode 100644 index 0000000000000000000000000000000000000000..db390ae4e2e94ef31673d3a47e1b9c6cac6ead3d --- /dev/null +++ b/prog/gtfs.py @@ -0,0 +1,76 @@ +import datetime +import csv + +from dataclasses import dataclass + +@dataclass +class Stop: + id: int + name: str + +@dataclass +class ShapePoint: + lat: str + lon: float + dist_traveled: float + +@dataclass +class TripStop: + stop: Stop + shape_dist_traveled: float + + +class GtfsDay(): + def __init__(self, date, data_getter): + self.date = date + self.data_getter = data_getter + self.stops = None + + async def get_file(self, name): + s = await self.data_getter(self.date, name) + return list(csv.DictReader(s.decode("utf-8").split("\n"))) + + async def load_stops(self): + if self.stops: return + d = await self.get_file("stops.txt") + if self.stops: return + self.stops = { + x["stop_id"]: Stop(x["stop_id"], x["stop_name"]) + for x in d + } + + async def get_shape_for_trip_id(self, trip_id): + for trip in await self.get_file("trips.txt"): + if trip["trip_id"] == trip_id: + shape_id = trip["shape_id"] + d = await self.get_file("shape_by_id/"+shape_id) + return [ ShapePoint( + float(x["shape_pt_lat"]), float(x["shape_pt_lon"]), float(x["shape_dist_traveled"]) + + ) for x in d ] + + async def get_stops_for_trip_id(self, trip_id): + await self.load_stops() + d = await self.get_file("stop_times.txt") + return [ TripStop( + self.stops[x["stop_id"]], + float(x["shape_dist_traveled"]) + ) for x in d if x["trip_id"] == trip_id] + + + + + + +default_data_getter = None + +for_date_cache = {} + +def for_date(date, data_getter=None): + if isinstance(date, datetime.datetime): + date = date.date() + if date not in for_date_cache: + for_date_cache[date] = GtfsDay(date, data_getter or default_data_getter) + return for_date_cache[date] + + diff --git a/prog/labeling-history-points.xml b/prog/labeling-history-points.xml new file mode 100644 index 0000000000000000000000000000000000000000..4f9e4875f3b9a5c2678015d6bca8e04f9eac51fa --- /dev/null +++ b/prog/labeling-history-points.xml @@ -0,0 +1,124 @@ +<labeling type="simple"> + <settings calloutType="simple"> + <text-style tabStopDistanceUnit="Point" forcedItalic="0" fontFamily="Open Sans" fontWordSpacing="0" fontItalic="0" fontSize="10" fontSizeMapUnitScale="3x:0,0,0,0,0,0" isExpression="1" fontStrikeout="0" forcedBold="0" capitalization="0" blendMode="0" textColor="255,1,1,255,rgb:1,0.00392156862745098,0.00392156862745098,1" textOpacity="1" fontKerning="1" fontUnderline="0" multilineHeight="1" fontWeight="50" fontLetterSpacing="0" fontSizeUnit="Point" stretchFactor="100" namedStyle="Regular" allowHtml="0" fieldName="'R:' || "repeated" || ' W:' || "without_data"" legendString="Aa" textOrientation="horizontal" previewBkgrdColor="255,255,255,255,rgb:1,1,1,1" tabStopDistance="80" useSubstitutions="0" multilineHeightUnit="Percentage" tabStopDistanceMapUnitScale="3x:0,0,0,0,0,0"> + <families/> + <text-buffer bufferNoFill="1" bufferColor="250,250,250,255,rgb:0.98039215686274506,0.98039215686274506,0.98039215686274506,1" bufferSizeMapUnitScale="3x:0,0,0,0,0,0" bufferJoinStyle="128" bufferBlendMode="0" bufferOpacity="1" bufferSizeUnits="MM" bufferDraw="0" bufferSize="1"/> + <text-mask maskSizeUnits="MM" maskSize="1.5" maskedSymbolLayers="" maskOpacity="1" maskJoinStyle="128" maskEnabled="0" maskSizeMapUnitScale="3x:0,0,0,0,0,0" maskSize2="1.5" maskType="0"/> + <background shapeSVGFile="" shapeType="0" shapeFillColor="255,255,255,255,rgb:1,1,1,1" shapeJoinStyle="64" shapeRadiiX="0" shapeSizeY="0" shapeRadiiMapUnitScale="3x:0,0,0,0,0,0" shapeBlendMode="0" shapeOffsetUnit="Point" shapeSizeType="0" shapeOffsetX="0" shapeBorderWidthMapUnitScale="3x:0,0,0,0,0,0" shapeOpacity="1" shapeRadiiUnit="Point" shapeOffsetY="0" shapeRadiiY="0" shapeSizeX="0" shapeSizeUnit="Point" shapeRotationType="0" shapeRotation="0" shapeOffsetMapUnitScale="3x:0,0,0,0,0,0" shapeBorderWidth="0" shapeSizeMapUnitScale="3x:0,0,0,0,0,0" shapeBorderWidthUnit="Point" shapeBorderColor="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" shapeDraw="1"> + <symbol name="markerSymbol" is_animated="0" clip_to_extent="1" frame_rate="10" force_rhr="0" type="marker" alpha="1"> + <data_defined_properties> + <Option type="Map"> + <Option name="name" value="" type="QString"/> + <Option name="properties"/> + <Option name="type" value="collection" type="QString"/> + </Option> + </data_defined_properties> + <layer pass="0" enabled="1" class="SimpleMarker" id="" locked="0"> + <Option type="Map"> + <Option name="angle" value="0" type="QString"/> + <Option name="cap_style" value="square" type="QString"/> + <Option name="color" value="145,82,45,255,rgb:0.56862745098039214,0.32156862745098042,0.17647058823529413,1" type="QString"/> + <Option name="horizontal_anchor_point" value="1" type="QString"/> + <Option name="joinstyle" value="bevel" type="QString"/> + <Option name="name" value="circle" type="QString"/> + <Option name="offset" value="0,0" type="QString"/> + <Option name="offset_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/> + <Option name="offset_unit" value="MM" type="QString"/> + <Option name="outline_color" value="35,35,35,255,rgb:0.13725490196078433,0.13725490196078433,0.13725490196078433,1" type="QString"/> + <Option name="outline_style" value="solid" type="QString"/> + <Option name="outline_width" value="0" type="QString"/> + <Option name="outline_width_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/> + <Option name="outline_width_unit" value="MM" type="QString"/> + <Option name="scale_method" value="diameter" type="QString"/> + <Option name="size" value="2" type="QString"/> + <Option name="size_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/> + <Option name="size_unit" value="MM" type="QString"/> + <Option name="vertical_anchor_point" value="1" type="QString"/> + </Option> + <data_defined_properties> + <Option type="Map"> + <Option name="name" value="" type="QString"/> + <Option name="properties"/> + <Option name="type" value="collection" type="QString"/> + </Option> + </data_defined_properties> + </layer> + </symbol> + <symbol name="fillSymbol" is_animated="0" clip_to_extent="1" frame_rate="10" force_rhr="0" type="fill" alpha="1"> + <data_defined_properties> + <Option type="Map"> + <Option name="name" value="" type="QString"/> + <Option name="properties"/> + <Option name="type" value="collection" type="QString"/> + </Option> + </data_defined_properties> + <layer pass="0" enabled="1" class="SimpleFill" id="" locked="0"> + <Option type="Map"> + <Option name="border_width_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/> + <Option name="color" value="255,255,255,255,rgb:1,1,1,1" type="QString"/> + <Option name="joinstyle" value="bevel" type="QString"/> + <Option name="offset" value="0,0" type="QString"/> + <Option name="offset_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/> + <Option name="offset_unit" value="MM" type="QString"/> + <Option name="outline_color" value="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" type="QString"/> + <Option name="outline_style" value="no" type="QString"/> + <Option name="outline_width" value="0" type="QString"/> + <Option name="outline_width_unit" value="Point" type="QString"/> + <Option name="style" value="solid" type="QString"/> + </Option> + <data_defined_properties> + <Option type="Map"> + <Option name="name" value="" type="QString"/> + <Option name="properties"/> + <Option name="type" value="collection" type="QString"/> + </Option> + </data_defined_properties> + </layer> + </symbol> + </background> + <shadow shadowOffsetMapUnitScale="3x:0,0,0,0,0,0" shadowOffsetGlobal="1" shadowRadius="1.5" shadowOpacity="0.69999999999999996" shadowColor="0,0,0,255,rgb:0,0,0,1" shadowOffsetDist="1" shadowRadiusUnit="MM" shadowRadiusAlphaOnly="0" shadowOffsetUnit="MM" shadowDraw="0" shadowOffsetAngle="135" shadowBlendMode="6" shadowUnder="0" shadowRadiusMapUnitScale="3x:0,0,0,0,0,0" shadowScale="100"/> + <dd_properties> + <Option type="Map"> + <Option name="name" value="" type="QString"/> + <Option name="properties"/> + <Option name="type" value="collection" type="QString"/> + </Option> + </dd_properties> + <substitutions/> + </text-style> + <text-format autoWrapLength="0" useMaxLineLengthForAutoWrap="1" multilineAlign="3" leftDirectionSymbol="<" placeDirectionSymbol="0" plussign="0" wrapChar="" reverseDirectionSymbol="0" formatNumbers="0" decimals="3" addDirectionSymbol="0" rightDirectionSymbol=">"/> + <placement maximumDistance="0" repeatDistanceMapUnitScale="3x:0,0,0,0,0,0" lineAnchorPercent="0.5" centroidWhole="0" allowDegraded="0" distMapUnitScale="3x:0,0,0,0,0,0" offsetUnits="MM" geometryGeneratorType="PointGeometry" labelOffsetMapUnitScale="3x:0,0,0,0,0,0" overrunDistanceMapUnitScale="3x:0,0,0,0,0,0" distUnits="MM" overrunDistance="0" rotationAngle="0" priority="5" lineAnchorType="0" xOffset="0" placement="6" prioritization="PreferCloser" rotationUnit="AngleDegrees" maximumDistanceMapUnitScale="3x:0,0,0,0,0,0" yOffset="0" repeatDistanceUnits="MM" offsetType="1" fitInPolygonOnly="0" preserveRotation="1" geometryGenerator="" lineAnchorTextPoint="FollowPlacement" quadOffset="4" maxCurvedCharAngleIn="25" layerType="PointGeometry" centroidInside="0" dist="0" repeatDistance="0" overlapHandling="PreventOverlap" predefinedPositionOrder="TR,TL,BR,BL,R,L,TSR,BSR" overrunDistanceUnit="MM" geometryGeneratorEnabled="0" maximumDistanceUnit="MM" maxCurvedCharAngleOut="-25" placementFlags="10" polygonPlacementFlags="2" lineAnchorClipping="0"/> + <rendering obstacle="1" scaleVisibility="0" unplacedVisibility="0" zIndex="0" obstacleType="1" mergeLines="0" scaleMax="0" drawLabels="1" fontMinPixelSize="3" obstacleFactor="1" upsidedownLabels="0" limitNumLabels="0" minFeatureSize="0" scaleMin="0" fontLimitPixelSize="0" labelPerPart="0" fontMaxPixelSize="10000" maxNumLabels="2000"/> + <dd_properties> + <Option type="Map"> + <Option name="name" value="" type="QString"/> + <Option name="properties"/> + <Option name="type" value="collection" type="QString"/> + </Option> + </dd_properties> + <callout type="simple"> + <Option type="Map"> + <Option name="anchorPoint" value="pole_of_inaccessibility" type="QString"/> + <Option name="blendMode" value="0" type="int"/> + <Option name="ddProperties" type="Map"> + <Option name="name" value="" type="QString"/> + <Option name="properties"/> + <Option name="type" value="collection" type="QString"/> + </Option> + <Option name="drawToAllParts" value="false" type="bool"/> + <Option name="enabled" value="0" type="QString"/> + <Option name="labelAnchorPoint" value="point_on_exterior" type="QString"/> + <Option name="lineSymbol" value="<symbol name="symbol" is_animated="0" clip_to_extent="1" frame_rate="10" force_rhr="0" type="line" alpha="1"><data_defined_properties><Option type="Map"><Option name="name" value="" type="QString"/><Option name="properties"/><Option name="type" value="collection" type="QString"/></Option></data_defined_properties><layer pass="0" enabled="1" class="SimpleLine" id="{2a0c46cd-8232-46a8-a05d-0402453f19ae}" locked="0"><Option type="Map"><Option name="align_dash_pattern" value="0" type="QString"/><Option name="capstyle" value="square" type="QString"/><Option name="customdash" value="5;2" type="QString"/><Option name="customdash_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/><Option name="customdash_unit" value="MM" type="QString"/><Option name="dash_pattern_offset" value="0" type="QString"/><Option name="dash_pattern_offset_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/><Option name="dash_pattern_offset_unit" value="MM" type="QString"/><Option name="draw_inside_polygon" value="0" type="QString"/><Option name="joinstyle" value="bevel" type="QString"/><Option name="line_color" value="60,60,60,255,rgb:0.23529411764705882,0.23529411764705882,0.23529411764705882,1" type="QString"/><Option name="line_style" value="solid" type="QString"/><Option name="line_width" value="0.3" type="QString"/><Option name="line_width_unit" value="MM" type="QString"/><Option name="offset" value="0" type="QString"/><Option name="offset_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/><Option name="offset_unit" value="MM" type="QString"/><Option name="ring_filter" value="0" type="QString"/><Option name="trim_distance_end" value="0" type="QString"/><Option name="trim_distance_end_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/><Option name="trim_distance_end_unit" value="MM" type="QString"/><Option name="trim_distance_start" value="0" type="QString"/><Option name="trim_distance_start_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/><Option name="trim_distance_start_unit" value="MM" type="QString"/><Option name="tweak_dash_pattern_on_corners" value="0" type="QString"/><Option name="use_custom_dash" value="0" type="QString"/><Option name="width_map_unit_scale" value="3x:0,0,0,0,0,0" type="QString"/></Option><data_defined_properties><Option type="Map"><Option name="name" value="" type="QString"/><Option name="properties"/><Option name="type" value="collection" type="QString"/></Option></data_defined_properties></layer></symbol>" type="QString"/> + <Option name="minLength" value="0" type="double"/> + <Option name="minLengthMapUnitScale" value="3x:0,0,0,0,0,0" type="QString"/> + <Option name="minLengthUnit" value="MM" type="QString"/> + <Option name="offsetFromAnchor" value="0" type="double"/> + <Option name="offsetFromAnchorMapUnitScale" value="3x:0,0,0,0,0,0" type="QString"/> + <Option name="offsetFromAnchorUnit" value="MM" type="QString"/> + <Option name="offsetFromLabel" value="0" type="double"/> + <Option name="offsetFromLabelMapUnitScale" value="3x:0,0,0,0,0,0" type="QString"/> + <Option name="offsetFromLabelUnit" value="MM" type="QString"/> + </Option> + </callout> + </settings> +</labeling> diff --git a/prog/main.py b/prog/main.py index 5541e76a362de629070566d6eca222d938a1553f..6b0f5de56c41620582042f93060d167d768ecf06 100755 --- a/prog/main.py +++ b/prog/main.py @@ -6,7 +6,7 @@ from qgis.PyQt.QtXml import QDomDocument, QDomElement from qgis.PyQt.QtWidgets import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtGui import * -import sip +import math from qasync import QEventLoop, asyncClose, asyncSlot, QApplication import asyncio @@ -17,8 +17,11 @@ import datetime import json import sys, os import pprint +import gtfs +window_title_prefix = "jr" + QgsApplication.setPrefixPath("/usr/", True) qgs = QgsApplication([], True) qgs.initQgis() @@ -36,16 +39,129 @@ class ClickableLabel(QLabel): self.clicked.emit() +def dist(a, b): + assert 40 < a[0] and a[0] < 60, a[0] + assert 5 < a[1] and a[1] < 25, a[1] + assert 40 < b[0] and b[0] < 60, b[0] + assert 5 < b[1] and b[1] < 25, b[1] + # lat lon + return math.sqrt(((a[0]-b[0])*111.2)**2 + ((a[1]-b[1])*71.50)**2) + communications = {} -async def get_communication(cmd='ssh hluk.fnuk.eu /mnt/jr/prog/run_py server.py'): +async def get_communication(cmd='ssh localhost /aux/jiri/jr/prog/run_py server.py'): if cmd not in communications: s = await communication.SSHRunSocket().connect(cmd) communications[cmd] = communication.DownloadServer(s) return communications[cmd] -class Vehicle: - def __init__(self, json): +async def gtfs_default_data_getter(date, filename): + c = await get_communication() + return await c.gtfs_get_file(date, filename) + +gtfs.default_data_getter = gtfs_default_data_getter + +def layer_updated(layer): + prov = layer.dataProvider() + layer.updateExtents() + layer.triggerRepaint() + prov.reloadData() + + + +class Trip: + def __init__(self, trip_id, date): + self.trip_id = trip_id + self.date = date + +class HistoryPoint: + def __init__(self, json, capture_time): + self.capture_time = capture_time self.json = json + self.state_position = json['properties']['last_position']['state_position'] + self.openapi_shape_dist_traveled = json['properties']['last_position']['shape_dist_traveled'] + self.lon, self.lat = json["geometry"]["coordinates"] + self.repeated = 0 + +class TripHistory: + def __init__(self, trip): + self.trip = trip + self.history = [] + + async def load_gtfs_shape(self): + self.gtfs_shape = await gtfs.for_date(self.trip.date).get_shape_for_trip_id(self.trip.trip_id) + self.stops = await gtfs.for_date(self.trip.date).get_stops_for_trip_id(self.trip.trip_id) + + + async def load_history(self, dt_from, dt_to): + tps = await get_data_of_trip(self.trip.trip_id, dt_from, dt_to) + + tps_new = [] + for dt, tp in tps: + self.add_history_point(dt, tp) + if tp is not None: + if len(tps_new) and tp["geometry"]["coordinates"] == tps_new[-1][1]["geometry"]["coordinates"]: + if tps_new[-1][1] != tp: + print("Same coordinates but different data:") + pprint.pp(tps_new[-1][1]) + print("---------------------") + pprint.pp(tp) + print("=====================") + tps_new[-1][2]["repeated"] += 1 + else: + tps_new.append((dt, tp, {"repeated": 0, "without_data": 0})) + else: + if len(tps_new): + tps_new[-1][2]["without_data"] += 1 + tps = tps_new + + def add_history_point(self, dt, json): + if json is not None: + if len(self.history) and json["geometry"]["coordinates"] == self.history[-1].json["geometry"]["coordinates"]: + if self.history[-1].json != json: + print("Same coordinates but different data:") + pprint.pp(self.history[-1].json) + print("---------------------") + pprint.pp(json) + print("=====================") + self.history[-1].repeated += 1 + else: + hp = HistoryPoint(json, dt) + if hp.state_position in ['on_track', 'at_stop']: + if len(self.history): + last_shape_point = self.history[-1].shape_point + if last_shape_point is None: + last_shape_point = 0 # We are on the begin of the track (last point was in before track state) + else: + last_shape_point = None # We don't know where we are + hp.shape_point = min( + range(len(self.gtfs_shape)), + key=lambda i: dist((hp.lat, hp.lon), (self.gtfs_shape[i].lat, self.gtfs_shape[i].lon)) + + (0.01*abs(self.gtfs_shape[i].dist_traveled - self.gtfs_shape[last_shape_point].dist_traveled) if last_shape_point is not None else 0) + ) + i = hp.shape_point + print(i, last_shape_point, dist((hp.lat, hp.lon), (self.gtfs_shape[i].lat, self.gtfs_shape[i].lon)), self.gtfs_shape[i].dist_traveled - self.gtfs_shape[last_shape_point].dist_traveled if last_shape_point is not None else None) + hp.shape_point_dist_traveled = self.gtfs_shape[hp.shape_point].dist_traveled + else: + hp.shape_point = hp.shape_point_dist_traveled = None + print(hp.state_position) + + self.history.append(hp) + + else: + if len(tps_new): + ... + # tps_new[-1][2]["without_data"] += 1 + + + + + + +class TripPoint: + def __init__(self, json, capture_time): + self.json = json + self.trip = Trip(json["properties"]["trip"]["gtfs"]["trip_id"], capture_time.date()) + self.capture_time = capture_time class MainWind(QMainWindow): @@ -60,7 +176,6 @@ class MainWind(QMainWindow): self.data_layer = QgsVectorLayer(uri, 'PID realtime', 'memory') - # self.data_layer.setAutoRefreshEnabled(True) # self.data_layer.setAutoRefreshInterval(500) d = QDomDocument() @@ -72,6 +187,7 @@ class MainWind(QMainWindow): # set the map canvas layer set self.map.canvas.setLayers([self.data_layer, orm, mapy_cz]) + self.map.addLayer(self.data_layer) self.feature_identifier = MainTool(self, self.map.canvas) @@ -81,6 +197,29 @@ class MainWind(QMainWindow): self.current_data = [] + self._tmp = QPushButton() + self._tmp.clicked.connect(self.tmp) + + dockWidget = QDockWidget("Dock Widget") + dockWidget.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) + dockWidget.setWidget(self._tmp) + self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget) + + @asyncSlot() + async def tmp(self): + dt = datetime.datetime(2024, 7, 4, 8, 4, 20) + await self.load_data(dt) + t = Trip('156_324_240701', dt.date()) + th = TripHistory(t) + await th.load_gtfs_shape() + await th.load_history(dt-datetime.timedelta(minutes=3), dt) + self.plot_gtfs_shape(th.trip, th.gtfs_shape) + self.plot_history_shape_mapping(th) + self.plot_history(th) + + history_graph = HistoryGraph(th) + history_graph.show() + tmp_windows[history_graph]=history_graph def set_date_time(self): @@ -98,10 +237,10 @@ class MainWind(QMainWindow): for fname, data in (await c.get_data(dt)).items(): proc = await asyncio.create_subprocess_exec("gunzip", stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE) stdout, stderr = await proc.communicate(data) - self.show_data(stdout) + self.show_data(stdout, dt) return - def show_data(self, source_json): + def show_data(self, source_json, capture_time): current_data = [] feats = [] @@ -129,9 +268,9 @@ class MainWind(QMainWindow): feat["vehicle_type"] = str(dato_vehicle_type["description_cs"]) feats.append(feat) - vehicle = Vehicle(json=dato) - vehicle.point = point - current_data.append(vehicle) + tp = TripPoint(json=dato, capture_time=capture_time) + tp.point = point + current_data.append(tp) self.current_data = current_data @@ -142,6 +281,143 @@ class MainWind(QMainWindow): self.data_layer.triggerRepaint() print("repaintRequested done") + def plot_gtfs_shape(self, trip, shape): + uri = "LineString" + shape_line_layer = QgsVectorLayer(uri, + f"Shape of {trip.trip_id}", + 'memory') + shape_line_layer.renderer().symbol().setWidth(1.3) + shape_line_layer.renderer().symbol().setColor(QColor.fromRgb(0, 255, 0)) + + shape_line_prov = shape_line_layer.dataProvider() + + shape_line_fields = shape_line_layer.fields() + feat = QgsFeature(shape_line_fields) + points = [] + for x in shape: + points.append(QgsPointXY(float(x.lon), float(x.lat))) + geometry = QgsGeometry.fromPolylineXY(points) + feat.setGeometry(geometry) + shape_line_prov.addFeatures([feat]) + + shape_line_layer.updateExtents() + shape_line_layer.triggerRepaint() + shape_line_prov.reloadData() + + self.map.addLayer(shape_line_layer) + + shape_line_layer.triggerRepaint() + print("plot_gtfs END") + + def plot_history(self, th: TripHistory): + # tps_new = [] + # for dt, tp in tps: + # if tp is not None: + # if len(tps_new) and tp["geometry"]["coordinates"] == tps_new[-1][1]["geometry"]["coordinates"]: + # if tps_new[-1][1] != tp: + # print("Same coordinates but different data:") + # pprint.pp(tps_new[-1][1]) + # print("---------------------") + # pprint.pp(tp) + # print("=====================") + # tps_new[-1][2]["repeated"] += 1 + # else: + # tps_new.append((dt, tp, {"repeated": 0, "without_data": 0})) + # else: + # if len(tps_new): + # tps_new[-1][2]["without_data"] += 1 + # tps = tps_new + + + + uri = "LineString" + history_line_layer = QgsVectorLayer(uri, + f"History of {th.trip.trip_id}", + 'memory') + history_line_layer.renderer().symbol().setWidth(1.3) + history_line_layer.renderer().symbol().setColor(QColor.fromRgb(255, 0, 0)) + + uri = "Point?field=repeated:int&field=without_data:int&index=yes" + history_points_layer = QgsVectorLayer(uri, + f"History of {th.trip.trip_id}", + 'memory') + d = QDomDocument() + d.setContent(open("labeling-history-points.xml").read()) + labeling = QgsAbstractVectorLayerLabeling.create(d.documentElement(), QgsReadWriteContext()) + history_points_layer.setLabeling(labeling) + history_points_layer.setLabelsEnabled(True) + + history_line_prov = history_line_layer.dataProvider() + history_points_prov = history_points_layer.dataProvider() + + history_line_fields = history_line_layer.fields() + history_points_fields = history_points_layer.fields() + + feat = QgsFeature(history_line_fields) + history_points_feats = [] + line_points = [] + for hp in th.history: + point = QgsPointXY(hp.lon, hp.lat) + line_points.append(point) + geometry = QgsGeometry.fromPolylineXY(line_points) + feat.setGeometry(geometry) + history_line_prov.addFeatures([feat]) + + + for hp in th.history: + feat = QgsFeature(history_points_fields) + point = QgsPointXY(hp.lon, hp.lat) + geometry = QgsGeometry.fromPointXY(point) + feat.setGeometry(geometry) + # feat["repeated"] = tp_data["repeated"] + # feat["without_data"] = tp_data["without_data"] + history_points_feats.append(feat) + history_points_prov.addFeatures(history_points_feats) + + history_line_layer.updateExtents() + history_line_layer.triggerRepaint() + history_points_layer.updateExtents() + history_points_layer.triggerRepaint() + + history_line_prov.reloadData() + history_points_prov.reloadData() + + + self.map.addLayer(history_line_layer) + self.map.addLayer(history_points_layer) + + def plot_history_shape_mapping(self, th: TripHistory): + uri = "LineString" + layer = QgsVectorLayer(uri, + f"Shape history mapping of {th.trip.trip_id}", + 'memory') + layer.renderer().symbol().setWidth(1.3) + layer.renderer().symbol().setColor(QColor.fromRgb(0, 0, 255)) + + prov = layer.dataProvider() + + fields = layer.fields() + feats = [] + for hp in th.history: + if hp.shape_point is not None: + feat = QgsFeature(fields) + x = th.gtfs_shape[hp.shape_point] + points = [ + QgsPointXY(hp.lon, hp.lat), + QgsPointXY(x.lon, x.lat) + ] + geometry = QgsGeometry.fromPolylineXY(points) + feat.setGeometry(geometry) + feats.append(feat) + + prov.addFeatures(feats) + + layer_updated(layer) + self.map.addLayer(layer) + + + + class SetDateTimeCaptureLabel(ClickableLabel): def __init__(self, dt, window_set_date_time): @@ -258,15 +534,102 @@ class SetDateTimeWind(QWidget): c = await get_communication() _10s_labels = [[[] for j in range(6)] for _ in range(60)] - dts = await c.list_realtime_data(date, date+datetime.timedelta(hours=1)) + dts = await c.list_realtime_data(date, date+datetime.timedelta(minutes=1)) - # TODO Check změny + if date != datetime.datetime.combine(self._calendar.selectedDate().toPyDate(), datetime.time(self.hour,0,0)): + return # Something changed during await for dt in dts: _10s_labels[dt.minute][dt.second//10].append(SetDateTimeCaptureLabel(dt, self)) self.update_10s_layouts(_10s_labels) +async def get_data_of_trip(trip_id, date_from, date_to): + c = await get_communication() + dts = await c.list_realtime_data(date_from, date_to) + out = [] + for dt in dts: + tc = None + print("GET", dt) + for fname, data in (await c.get_data(dt)).items(): + proc = await asyncio.create_subprocess_exec("gunzip", stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE) + stdout, stderr = await proc.communicate(data) + data = json.loads(stdout) + for dato in data["features"]: + if dato["properties"]["trip"]["gtfs"]["trip_id"] == trip_id: + tc = dato + break + out.append((dt, tc)) + return out + + + +class SmallTripPointLabel(ClickableLabel): + def __init__(self, tp: TripPoint, on_click=None): + super().__init__() + if on_click: + self.on_click = on_click + self.clicked.connect(self.on_click) + self.tp = tp + route_short_name = tp.json["properties"]["trip"]["gtfs"]["route_short_name"] + trip_id = tp.json["properties"]["trip"]["gtfs"]["trip_id"] + self.setText(f"{route_short_name} – {trip_id}") + +class SmallTripPointLabelSelector(SmallTripPointLabel): + def __init__(self, tp: TripPoint, trip_point_selector): + super().__init__(tp) + self.trip_point_selector = trip_point_selector + self.clicked.connect(self.on_click) + + def on_click(self): + TripPointWindow(self.tp, self.trip_point_selector.mwnd) + self.trip_point_selector.hide() + + + +class TripPointSelector(QWidget): + def __init__(self, tps, mwnd): + self.mwnd = mwnd + super().__init__() + self.tps = tps + self._layout = QVBoxLayout() + self.setLayout(self._layout) + self._labels = [SmallTripPointLabelSelector(tp, self) for tp in tps] + for l in self._labels: + self._layout.addWidget(l) + self.setWindowTitle(f"{window_title_prefix} - point selector") + self.show() + tmp_windows[self] = self + + +tmp_windows = {} + +class TripPointWindow(QWidget): + def __init__(self, tp, mwnd): + super().__init__() + self.mwnd = mwnd + self._layout = QVBoxLayout() + self.setLayout(self._layout) + self.tp = tp + self._tmp = SmallTripPointLabel(tp) + self._layout.addWidget(self._tmp) + self.setLayout(self._layout) + self.setWindowTitle(f"{window_title_prefix} - trip point TODO") + self.show() + tmp_windows[self] = self + + self._plot_history_button = QPushButton("Plot history") + self._layout.addWidget(self._plot_history_button) + self._plot_history_button.clicked.connect(self.plot_history) + + self._json = QLabel(pprint.pformat(tp.json)) + self._layout.addWidget(self._json) + + @asyncSlot() + async def plot_history(self): + await self.mwnd.plot_gtfs_shape(self.tp) + await self.mwnd.plot_history(self.tp, self.tp.capture_time-datetime.timedelta(minutes=30), self.tp.capture_time) + @@ -278,13 +641,19 @@ class MainTool(QgsMapTool): def canvasClicked(self, e): pt = e.mapPoint() data_by_dist = sorted([(crs_transform.transform(x.point).distance(pt), x) for x in self.wnd.current_data], key=lambda x:x[0])[:10] - for d, x in data_by_dist: - print(d, pt, x.point) + + scale = self.wnd.map.canvas.scale() + data_near = [(d, x) for d, x in data_by_dist if d/scale < 0.005] + tp_selector = TripPointSelector([x for d, x in data_near], self.wnd) + for d, x in data_near: + print() + print(d/scale, pt, x.point) pprint.pp(x.json) + print() - def canvasMoveEvent(self, e): - print("canvasMoveEvent", e) + #def canvasMoveEvent(self, e): + #print("canvasMoveEvent", e) def canvasPressEvent(self, e): print("canvasPressEvent", e) @@ -296,6 +665,7 @@ class MainTool(QgsMapTool): + class MapWidget(QWidget): def __init__(self): QWidget.__init__(self) @@ -344,6 +714,8 @@ class MapWidget(QWidget): self.canvas.setDestinationCrs(mapy_cz.crs()) + self.data_layers = [] + async def zoomIn(self): self.canvas.setMapTool(self.toolZoomIn) @@ -354,6 +726,13 @@ class MapWidget(QWidget): def pan(self): self.canvas.setMapTool(self.toolPan) + def addLayer(self, layer): + self.data_layers.append(layer) + self.pushLayers() + + def pushLayers(self): + self.canvas.setLayers(list(reversed(self.data_layers)) + [mapy_cz]) + # create Qt application @@ -367,10 +746,77 @@ mapy_cz = QgsRasterLayer('http-header:referer=https://mapy.cz/ &referer=https:/ # create main window wnd = MainWind() canvas = wnd.map.canvas +wnd.setWindowTitle(f"{window_title_prefix}") wnd.show() +from matplotlib.backends.backend_qt5 \ + import FigureCanvas as FigureCanvas +from matplotlib.backends.backend_qt5 \ + import NavigationToolbar2 as NavigationToolbar +import matplotlib.pyplot as plt + + + + + +import sys +import time + +import numpy as np + +from matplotlib.backends.backend_qtagg import FigureCanvas +from matplotlib.backends.backend_qtagg import \ + NavigationToolbar2QT as NavigationToolbar +from matplotlib.figure import Figure + + +class Graph(QWidget): + def __init__(self): + super().__init__() + layout = QVBoxLayout() + self.setLayout(layout) + + + canvas = FigureCanvas(Figure(figsize=(5, 3))) + layout.addWidget(canvas) + layout.addWidget(NavigationToolbar(canvas, self)) + + self.ax = canvas.figure.subplots() + + +class HistoryGraph(Graph): + def __init__(self, th: TripHistory): + super().__init__() + x = [] + y = [] + for hp in th.history: + if hp.shape_point_dist_traveled is not None: + x.append(hp.shape_point_dist_traveled) + y.append(hp.capture_time) + self._line, = self.ax.plot(x, y) + self._line.figure.canvas.draw() + + xticks = [] + xlabels = [] + for stop in th.stops: + x = stop.shape_dist_traveled + self.ax.axvline(x=x, ymin=0, ymax=1, color="black") + #self.ax.xlabel(xy =(((x-x_bounds[0])/(x_bounds[1]-x_bounds[0])),1.01), xycoords='axes fraction', verticalalignment='top', horizontalalignment='center' , rotation = 270, text=stop.stop.name) + #self.ax.text(x, 0.5, stop.stop.name, rotation=90, transform=ax.get_xaxis_text1_transform(0)[0]) + #self.ax.text(x, 0.5, stop.stop.name, rotation=90, transform=ax.get_xaxis_text1_transform(0)[0]) + xticks.append(x) + xlabels.append(stop.stop.name) + self.ax.set_xticks(xticks) + self.ax.set_xticklabels(xlabels) + + + +#tmp = HistoryGraph() +#tmp.show() + + # run! event_loop = QEventLoop(app) asyncio.set_event_loop(event_loop)