diff --git a/docs/Web-Mapping-and-Geovisualisation.pdf b/docs/Web-Mapping-and-Geovisualisation.pdf index 9d8cc6c..96f77ad 100644 Binary files a/docs/Web-Mapping-and-Geovisualisation.pdf and b/docs/Web-Mapping-and-Geovisualisation.pdf differ diff --git a/docs/labs/w09_advanced_files/figure-html/cell-14-output-1.png b/docs/labs/w09_advanced_files/figure-html/cell-14-output-1.png new file mode 100644 index 0000000..cb94809 Binary files /dev/null and b/docs/labs/w09_advanced_files/figure-html/cell-14-output-1.png differ diff --git a/docs/labs/w09_advanced_files/figure-html/cell-16-output-1.png b/docs/labs/w09_advanced_files/figure-html/cell-16-output-1.png index cb94809..623bf98 100644 Binary files a/docs/labs/w09_advanced_files/figure-html/cell-16-output-1.png and b/docs/labs/w09_advanced_files/figure-html/cell-16-output-1.png differ diff --git a/docs/labs/w09_advanced_files/figure-html/cell-18-output-1.png b/docs/labs/w09_advanced_files/figure-html/cell-18-output-1.png deleted file mode 100644 index 623bf98..0000000 Binary files a/docs/labs/w09_advanced_files/figure-html/cell-18-output-1.png and /dev/null differ diff --git a/docs/labs/w09_advanced_files/figure-html/cell-10-output-1.png b/docs/labs/w09_advanced_files/figure-html/cell-8-output-1.png similarity index 100% rename from docs/labs/w09_advanced_files/figure-html/cell-10-output-1.png rename to docs/labs/w09_advanced_files/figure-html/cell-8-output-1.png diff --git a/docs/search.json b/docs/search.json index ab8634d..675e5de 100644 --- a/docs/search.json +++ b/docs/search.json @@ -494,7 +494,7 @@ "href": "labs/w09_advanced.html#getting-gps-trajectories-from-openstreetmap-data", "title": "8  GPS Traces and Animations", "section": "", - "text": "Trace ID (unique identifier for the trace)\nTrace Name (optional name provided by the user)\nUploader Username\nUpload Date\nTrace Summary (e.g., distance, duration)\n\n\n\n\n8.1.1 Downaloding GPS traces from the OpenStreetMap\n\ndef bbox_to_osm_format(bbox):\n \"\"\"\n Convert bounding box coordinates to OSM API format.\n\n Parameters\n ----------\n bbox: tuple\n A tuple containing the bounding box coordinates in the order (north, south, east, west).\n\n Returns\n -------\n bbox_str: str\n A string representing the bounding box in the format \"west,south,east,north\".\n \"\"\"\n north, south, east, west = bbox\n bbox = f\"{west},{south},{east},{north}\"\n return bbox\n\nNow trying to get data around Liverpool Campus. Feel free to change area.\n\nimport osmnx as ox\n# Define the place name\nplace_name = \"University of Liverpool, UK\"\n\n# Get the latitude and longitude\nlatitude, longitude = ox.geocoder.geocode(place_name)\n\n# Create the bbox\nbbox = ox.utils_geo.bbox_from_point((latitude, longitude), dist = 1500)\nbbox = bbox_to_osm_format(bbox) # needs to be {west},{south},{east},{north} for OSM Api\n\n\ndef get_osm_traces(max_pages = 2, bbox='16.18, 48.09, 16.61, 48.32'):\n \"\"\"\n Retrieve OpenStreetMap GPS traces within a specified bounding box.\n\n Parameters\n ----------\n max_pages: int, optional\n The maximum number of pages to retrieve. Defaults to 2.\n bbox: str, optional\n The bounding box coordinates in the format 'west, south, east, north'. Defaults to '16.18, 48.09, 16.61, 48.32'.\n\n Returns\n -------\n final_gdf: GeoDataFrame\n A GeoDataFrame containing the retrieved GPS traces.\n \"\"\"\n \n all_data = []\n page = 0\n \n while (True) and (page <= max_pages):\n # Constructing the URL to query OpenStreetMap API for GPS trackpoints within a specified bounding box and page number\n url = f'https://api.openstreetmap.org/api/0.6/trackpoints?bbox={bbox}&page={page}'\n \n # Sending a GET request to the constructed URL\n response = requests.get(url)\n \n # Checking if the response status code is not 200 (indicating success) or if the response content is empty\n # If either condition is met, the loop breaks, indicating no more data to retrieve\n if response.status_code != 200 or not response.content:\n break\n \n # Reading the content of the response, which contains GPS trackpoints data, into a GeoDataFrame\n # The 'layer' parameter specifies the layer within the GeoDataFrame where trackpoints will be stored\n gdf = gpd.read_file(BytesIO(response.content), layer='track_points')\n\n if gdf.empty:\n break\n \n all_data.append(gdf)\n page += 1\n \n # Concatenate all GeoDataFrames into one\n final_gdf = gpd.GeoDataFrame(pd.concat(all_data, ignore_index=True))\n \n # dropping empty columns\n columns_to_drop = ['ele', 'course', 'speed', 'magvar', 'geoidheight', 'name', 'cmt', 'desc',\n 'src', 'url', 'urlname', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop',\n 'pdop', 'ageofdgpsdata', 'dgpsid']\n \n final_gdf = final_gdf.drop(columns=[col for col in columns_to_drop if col in final_gdf.columns])\n return final_gdf\n\nWe initial download 2 pages of GSP traces. We can try with higher numbers to get more data. More recent traces are downloaded first.\n\nmax_pages = 2 # pages of data, \ngps_track_points = get_osm_traces(max_pages, bbox)\n\n\ngps_track_points.head()\n\n\n\n\n\n\n\n\ntrack_fid\ntrack_seg_id\ntrack_seg_point_id\ntime\ngeometry\n\n\n\n\n0\n0\n0\n0\n2023-09-24 10:41:39+00:00\nPOINT (-2.98825 53.40690)\n\n\n1\n0\n0\n1\n2023-09-24 10:41:46+00:00\nPOINT (-2.98793 53.40708)\n\n\n2\n0\n0\n2\n2023-09-24 10:41:47+00:00\nPOINT (-2.98408 53.40931)\n\n\n3\n0\n0\n3\n2023-09-24 10:41:49+00:00\nPOINT (-2.98391 53.40927)\n\n\n4\n0\n0\n4\n2023-09-24 10:41:50+00:00\nPOINT (-2.98378 53.40933)\n\n\n\n\n\n\n\n\ngps_track_points.plot(markersize = 0.5) # still are trackpoints still\n\n\n\n\n\n\n\n\n\n\n8.1.2 Transform the Point GeoDataFrame in a LineString GeoDataFrame with MovingPandas\nMovingPandas is a Python library designed for analyzing movement data using Pandas. It extends Pandas’ capabilities to handle temporal and spatial data jointly. It is the ideal library for trajectory data analysis, visualization, and exploration. MovingPandas provides functionalities for processing and analyzing movement data, including trajectory segmentation, trajectory aggregation, and trajectory generalization.\nSee examples and documentation at https://movingpandas.org\nThe following code creates a TrajectoryCollection object by providing the GPS track points data, specifying the column that identifies trajectories, and indicating the column containing timestamps.\n\nosm_traces = mpd.TrajectoryCollection(gps_track_points, 'track_fid', t='time')\nprint(f'The OSM traces download contains {len(osm_traces)} tracks')\n\nThe OSM traces download contains 14 tracks\n\n\n\nfor track in osm_traces: \n print(f'Track {track.id}: length={track.get_length(units=\"km\"):.2f} km')\n\nTrack 0: length=9.96 km\nTrack 1: length=3.02 km\nTrack 2: length=2.84 km\nTrack 3: length=2.72 km\nTrack 4: length=2.92 km\nTrack 5: length=3.29 km\nTrack 6: length=4.95 km\nTrack 7: length=4.12 km\nTrack 8: length=4.87 km\nTrack 9: length=1.21 km\nTrack 10: length=0.03 km\nTrack 11: length=0.00 km\nTrack 12: length=0.70 km\nTrack 13: length=0.36 km\n\n\nBelow we set up some visualization options for holoviews. Holoviews is a library for creating interactive visualizations of complex datasets with ease. Holoviews provides a declarative approach to building visualizations, allowing you to express your visualization intent concisely. Holoviews combines the usage of Matplotlib, Bokeh, and Plotly and supports creating interactive visualizations. You can easily add interactive elements like hover tooltips, zooming, panning, and selection widgets to your plots. Compared to folium, however, it does not allow for as much freedom and development outside the box. Folium is primarily designed for creating web-based maps with features like tile layers and GeoJSON overlays, offering a high degree of customization for map-based visualizations. Holoviews, on the other hand, focuses more on general-purpose interactive visualizations beyond just maps, offering a broader range of visualization options.\nSee examples and documentation at https://holoviews.org.\n\nfrom holoviews import opts, dim\nimport hvplot.pandas \n\nplot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}\nopts.defaults(opts.Overlay(active_tools=['wheel_zoom'], frame_width=500, frame_height=400))\nhvplot_defaults = {'tiles':None, 'cmap':'Viridis', 'colorbar':True}\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nTraces generalisation\nThe MinTimeDeltaGeneralizer class from MovingPandas is applied to the osm_traces object. This class is responsible for simplifying or generalizing the GPS traces based on a minimum time delta. In this case, traces are being generalized to a tolerance of one minute.\n\nfrom datetime import datetime, timedelta\nosm_traces = mpd.MinTimeDeltaGeneralizer(osm_traces).generalize(tolerance=timedelta(minutes=1))\nosm_traces.hvplot(title='OSM Traces', line_width=3, width=700, height=400)\n\n\n\n\n\n \n\n\n\n\nVisualising by speed\n\nosm_traces.get_trajectory(0).add_speed(overwrite=True, units=(\"km\",\"h\"))\nosm_traces.get_trajectory(0).hvplot(\n title='Speed (km/h) along track', c='speed', cmap='RdYlBu',\n line_width=3, width=700, height=400, tiles='CartoLight', colorbar=True)\n\n\n\n\n\n \n\n\n\n\nOne can also convert the TrajectoryCollection (MovingPandas’ object) to a LineString GeoDataFrame. This would allow us to take advantage of what we learnt across the previous sessions. For example, we can plot through GeoPandas and matplotlib after having projected the layer.\n\ncrs = 'EPSG:27700'\ngps_tracks = osm_traces.to_traj_gdf()\ngps_tracks = gps_tracks.set_crs(\"EPSG:4326\").to_crs(crs)\ngps_tracks.plot()\n\n\n\n\n\n\n\n\nAs you can see, from both visualisations, OSM traces may include also odd traces that jump from different locations of the city.\n\nExercise: Explore the gps_tracks dataset and try to compute the average speed of each of the tracks. Try to understand, also using the gps_track_points (do not forget to project it, in case), if you can find a way to filter out traces that follow very odd routes.", + "text": "Trace ID (unique identifier for the trace)\nTrace Name (optional name provided by the user)\nUploader Username\nUpload Date\nTrace Summary (e.g., distance, duration)\n\n\n\n\n8.1.1 Downaloding GPS traces from the OpenStreetMap\ndef bbox_to_osm_format(bbox): ““” Convert bounding box coordinates to OSM API format.\nParameters\n----------\nbbox: tuple\n A tuple containing the bounding box coordinates in the order (north, south, east, west).\n\nReturns\n-------\nbbox_str: str\n A string representing the bounding box in the format \"west,south,east,north\".\n\"\"\"\nnorth, south, east, west = bbox\nbbox = f\"{west},{south},{east},{north}\"\nreturn bbox\nWe can try to get OSM traces around the Uni of Liverpool Campus. Feel free to change the area.\n\nimport osmnx as ox\n# Define the place name\nplace_name = \"University of Liverpool, UK\"\n\n# Get the latitude and longitude\nlatitude, longitude = ox.geocoder.geocode(place_name)\n\n# Create the bbox\nbbox = ox.utils_geo.bbox_from_point((latitude, longitude), dist = 1500)\nbbox = bbox_to_osm_format(bbox) # needs to be {west},{south},{east},{north} for OSM Api\n\nThe get_osm_traces function below retrieves GPS traces from OpenStreetMap within a specified bounding box, processing up to a user-defined maximum number of pages. It uses a while loop to fetch and parse GPS data into GeoDataFrames, querying the OSM API by incrementally updating the page number until no more data is available or the maximum page limit is reached.\nUpon fetching the data, the function concatenates these individual GeoDataFrames into a single comprehensive GeoDataFrame. Before returning the final GeoDataFrame, it cleans the dataset by dropping a predefined list of potentially irrelevant or empty columns.\n\ndef get_osm_traces(max_pages = 2, bbox='16.18, 48.09, 16.61, 48.32'):\n \"\"\"\n Retrieve OpenStreetMap GPS traces within a specified bounding box.\n\n Parameters\n ----------\n max_pages: int, optional\n The maximum number of pages to retrieve. Defaults to 2.\n bbox: str, optional\n The bounding box coordinates in the format 'west, south, east, north'. Defaults to '16.18, 48.09, 16.61, 48.32'.\n\n Returns\n -------\n final_gdf: GeoDataFrame\n A GeoDataFrame containing the retrieved GPS traces.\n \"\"\"\n \n all_data = []\n page = 0\n \n while (True) and (page <= max_pages):\n # Constructing the URL to query OpenStreetMap API for GPS trackpoints within a specified bounding box and page number\n url = f'https://api.openstreetmap.org/api/0.6/trackpoints?bbox={bbox}&page={page}'\n \n # Sending a GET request to the constructed URL\n response = requests.get(url)\n \n # Checking if the response status code is not 200 (indicating success) or if the response content is empty\n # If either condition is met, the loop breaks, indicating no more data to retrieve\n if response.status_code != 200 or not response.content:\n break\n \n # Reading the content of the response, which contains GPS trackpoints data, into a GeoDataFrame\n # The 'layer' parameter specifies the layer within the GeoDataFrame where trackpoints will be stored\n gdf = gpd.read_file(BytesIO(response.content), layer='track_points')\n\n if gdf.empty:\n break\n \n all_data.append(gdf)\n page += 1\n \n # Concatenate all GeoDataFrames into one\n final_gdf = gpd.GeoDataFrame(pd.concat(all_data, ignore_index=True))\n \n # dropping empty columns\n columns_to_drop = ['ele', 'course', 'speed', 'magvar', 'geoidheight', 'name', 'cmt', 'desc',\n 'src', 'url', 'urlname', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop',\n 'pdop', 'ageofdgpsdata', 'dgpsid']\n \n final_gdf = final_gdf.drop(columns=[col for col in columns_to_drop if col in final_gdf.columns])\n return final_gdf\n\nWe initially download 2 pages of GSP traces. We can try with higher numbers to get more data. More recent traces are downloaded first.\n\nmax_pages = 2 # pages of data, \ngps_track_points = get_osm_traces(max_pages, bbox)\n\n\ngps_track_points.head()\n\n\n\n\n\n\n\n\ntrack_fid\ntrack_seg_id\ntrack_seg_point_id\ntime\ngeometry\n\n\n\n\n0\n0\n0\n0\n2023-09-24 10:41:39+00:00\nPOINT (-2.98825 53.40690)\n\n\n1\n0\n0\n1\n2023-09-24 10:41:46+00:00\nPOINT (-2.98793 53.40708)\n\n\n2\n0\n0\n2\n2023-09-24 10:41:47+00:00\nPOINT (-2.98408 53.40931)\n\n\n3\n0\n0\n3\n2023-09-24 10:41:49+00:00\nPOINT (-2.98391 53.40927)\n\n\n4\n0\n0\n4\n2023-09-24 10:41:50+00:00\nPOINT (-2.98378 53.40933)\n\n\n\n\n\n\n\n\ngps_track_points.plot(markersize = 0.5) # still are trackpoints still\n\n\n\n\n\n\n\n\n\n\n8.1.2 Transform the Point GeoDataFrame in a LineString GeoDataFrame with MovingPandas\nMovingPandas is a Python library designed for analyzing movement data using Pandas. It extends Pandas’ capabilities to handle temporal and spatial data jointly. It is the ideal library for trajectory data analysis, visualization, and exploration. MovingPandas provides functionalities for processing and analyzing movement data, including trajectory segmentation, trajectory aggregation, and trajectory generalization.\nSee examples and documentation at https://movingpandas.org\nThe following code creates a TrajectoryCollection object by providing the GPS track points data, specifying the column that identifies trajectories, and indicating the column containing timestamps.\n\nosm_traces = mpd.TrajectoryCollection(gps_track_points, 'track_fid', t='time')\nprint(f'The OSM traces download contains {len(osm_traces)} tracks')\n\nThe OSM traces download contains 14 tracks\n\n\n\nfor track in osm_traces: \n print(f'Track {track.id}: length={track.get_length(units=\"km\"):.2f} km')\n\nTrack 0: length=9.96 km\nTrack 1: length=3.02 km\nTrack 2: length=2.84 km\nTrack 3: length=2.72 km\nTrack 4: length=2.92 km\nTrack 5: length=3.29 km\nTrack 6: length=4.95 km\nTrack 7: length=4.12 km\nTrack 8: length=4.87 km\nTrack 9: length=1.21 km\nTrack 10: length=0.03 km\nTrack 11: length=0.00 km\nTrack 12: length=0.70 km\nTrack 13: length=0.36 km\n\n\nBelow we set up some visualization options for holoviews. Holoviews is a library for creating interactive visualizations of complex datasets with ease. Holoviews provides a declarative approach to building visualizations, allowing you to express your visualization intent concisely. Holoviews combines the usage of Matplotlib, Bokeh, and Plotly and supports creating interactive visualizations. You can easily add interactive elements like hover tooltips, zooming, panning, and selection widgets to your plots. Compared to folium, however, it does not allow for as much freedom and development outside the box. Folium is primarily designed for creating web-based maps with features like tile layers and GeoJSON overlays, offering a high degree of customization for map-based visualizations. Holoviews, on the other hand, focuses more on general-purpose interactive visualizations beyond just maps, offering a broader range of visualization options.\nSee examples and documentation at https://holoviews.org.\n\nfrom holoviews import opts, dim\nimport hvplot.pandas \n\nplot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}\nopts.defaults(opts.Overlay(active_tools=['wheel_zoom'], frame_width=500, frame_height=400))\nhvplot_defaults = {'tiles':None, 'cmap':'Viridis', 'colorbar':True}\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\nTraces generalisation\nThe MinTimeDeltaGeneralizer class from MovingPandas is applied to the osm_traces object. This class is responsible for simplifying or generalizing the GPS traces based on a minimum time delta. In this case, traces are being generalized to a tolerance of one minute.\n\nfrom datetime import datetime, timedelta\nosm_traces = mpd.MinTimeDeltaGeneralizer(osm_traces).generalize(tolerance=timedelta(minutes=1))\nosm_traces.hvplot(title='OSM Traces', line_width=3, width=700, height=400)\n\n\n\n\n\n \n\n\n\n\nVisualising by speed\n\nosm_traces.get_trajectory(0).add_speed(overwrite=True, units=(\"km\",\"h\"))\nosm_traces.get_trajectory(0).hvplot(\n title='Speed (km/h) along track', c='speed', cmap='RdYlBu',\n line_width=3, width=700, height=400, tiles='CartoLight', colorbar=True)\n\n\n\n\n\n \n\n\n\n\nOne can also convert the TrajectoryCollection (MovingPandas’ object) to a LineString GeoDataFrame. This would allow us to take advantage of what we learnt across the previous sessions. For example, we can plot through GeoPandas and matplotlib after having projected the layer.\n\ncrs = 'EPSG:27700'\ngps_tracks = osm_traces.to_traj_gdf()\ngps_tracks = gps_tracks.set_crs(\"EPSG:4326\").to_crs(crs)\ngps_tracks.plot()\n\n\n\n\n\n\n\n\nAs you can see, from both visualisations, OSM traces may include also odd traces that jump from different locations of the city.\n\nExercise: Explore the gps_tracks dataset and try to compute the average speed of each of the tracks. Try to understand, also using the gps_track_points (do not forget to project it, in case), if you can find a way to filter out traces that follow very odd routes.", "crumbs": [ "8  GPS Traces and Animations" ] diff --git a/docs/sitemap.xml b/docs/sitemap.xml index cd44f45..dff5784 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -50,10 +50,10 @@ https://gdsl-ul.github.io/wma/labs/w09_advanced.html - 2024-04-16T11:04:48.482Z + 2024-04-16T11:25:57.632Z https://gdsl-ul.github.io/wma/Web-Mapping-and-Geovisualisation.pdf - 2024-04-16T11:08:30.082Z + 2024-04-16T11:30:52.435Z