OGC MovingFeatures functionality¶

No description has been provided for this image

Ressources:

  • OGC Moving Features Standard Working Group on Github
  • Moving Features JSON Encoding Standard
    • Sample files on Github

MovingPandas offers OGC MovingFeatures functionality to read and convert MF-JSON files.

In [1]:
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas
import json

from geopandas import GeoDataFrame, read_file
from movingpandas import read_mf_dict, gdf_to_mf_json
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts, dim

import warnings

warnings.filterwarnings("ignore")

opts.defaults(opts.Overlay(active_tools=["wheel_zoom"]))

mpd.show_versions()
MovingPandas 0.20.0

SYSTEM INFO
-----------
python     : 3.10.15 | packaged by conda-forge | (main, Oct 16 2024, 01:15:49) [MSC v.1941 64 bit (AMD64)]
executable : c:\Users\Agarkovam\AppData\Local\miniforge3\envs\mpd-ex\python.exe
machine    : Windows-10-10.0.19045-SP0

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : None
GDAL data dir: None
PROJ       : 9.5.0
PROJ data dir: C:\Users\Agarkovam\AppData\Local\miniforge3\envs\mpd-ex\Library\share\proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 1.0.1
pandas     : 2.2.3
fiona      : None
numpy      : 1.23.1
shapely    : 2.0.6
pyproj     : 3.7.0
matplotlib : 3.9.2
mapclassify: 2.8.1
geopy      : 2.4.1
holoviews  : 1.20.0
hvplot     : 0.11.1
geoviews   : 1.13.0
stonesoup  : 1.4

MF-JSON MovingPoint¶

https://docs.ogc.org/is/19-045r3/19-045r3.html#_mf_json_prism_encoding

https://github.com/opengeospatial/mf-json/tree/master/json-sample/movingpoint

In [2]:
traj = mpd.read_mf_json("../data/mf-movingpoint.json")
traj.df.head()
Out[2]:
preasure wind class geometry traj_id
t
2018-12-31 06:00:00 1004.0 0.0 2.0 POINT (111.9 7.6) 0
2018-12-31 12:00:00 1004.0 0.0 2.0 POINT (111.3 7.3) 0
2018-12-31 18:00:00 1004.0 0.0 2.0 POINT (111.1 7) 0
2019-01-01 00:00:00 1004.0 0.0 2.0 POINT (110.7 6.6) 0
2019-01-01 06:00:00 1000.0 35.0 3.0 POINT (110.2 6.3) 0
In [3]:
traj.explore(color="red")
Out[3]:
Make this Notebook Trusted to load map: File -> Trust Notebook

MF-JSON MovingFeatureCollection¶

https://docs.ogc.org/is/19-045r3/19-045r3.html#_mf_json_prism_encoding

In [4]:
collection = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {"id": 5},
            "temporalGeometry": {
                "type": "MovingPoint",
                "datetimes": ["2008-02-02T15:02:18Z", "2008-02-02T18:32:28Z"],
                "coordinates": [[116.52299, 40.07757], [116.52302, 39.92129]],
            },
        }
    ],
}
In [5]:
trajs_collection = read_mf_dict(collection, traj_id_property="id")
trajs_collection
Out[5]:
TrajectoryCollection with 1 trajectories

Convert TrajectoryCollection to a dict compatible with MF-JSON¶

In [6]:
df = pd.DataFrame(
    {
        "t": pd.date_range("2020-01-01", periods=5, freq="min"),
        "trajectory_id": [1, 1, 2, 2, 2],
        "geometry": [Point(0, 0), Point(0, 1), Point(1, 2), Point(1, 3), Point(2, 4)],
    }
)
gdf = gpd.GeoDataFrame(df, crs=4326)
tc = mpd.TrajectoryCollection(gdf, traj_id_col="trajectory_id", t="t")
tc
Out[6]:
TrajectoryCollection with 2 trajectories
In [7]:
mf_json = tc.to_mf_json()
mf_json
Out[7]:
{'type': 'FeatureCollection',
 'features': [{'type': 'Feature',
   'properties': {'trajectory_id': 1},
   'temporalGeometry': {'type': 'MovingPoint',
    'coordinates': [(0.0, 0.0), (0.0, 1.0)],
    'datetimes': ['2020-01-01 00:00:00', '2020-01-01 00:01:00']}},
  {'type': 'Feature',
   'properties': {'trajectory_id': 2},
   'temporalGeometry': {'type': 'MovingPoint',
    'coordinates': [(1.0, 2.0), (1.0, 3.0), (2.0, 4.0)],
    'datetimes': ['2020-01-01 00:02:00',
     '2020-01-01 00:03:00',
     '2020-01-01 00:04:00']}}]}

MF-JSON Trajectory¶

https://docs.ogc.org/is/19-045r3/19-045r3.html#_mf_json_trajectory_encoding

https://github.com/opengeospatial/mf-json/tree/master/json-sample/trajectory

In [8]:
traj = mpd.read_mf_json("../data/mf-trajectory.json", traj_id=3)
traj.df.head()
Out[8]:
preasure wind class geometry traj_id
t
2019-08-05 00:00:00 1004.0 0.0 2.0 POINT (147.7 15.6) 3
2019-08-05 06:00:00 1000.0 0.0 2.0 POINT (146.5 16.1) 3
2019-08-05 12:00:00 1000.0 0.0 2.0 POINT (145.2 16.8) 3
2019-08-05 18:00:00 1000.0 0.0 2.0 POINT (144 17.5) 3
2019-08-06 00:00:00 1000.0 0.0 2.0 POINT (143.4 18) 3
In [9]:
traj.explore(color="green")
Out[9]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Writing MF-JSON¶

Convert Trajectory to a dict compatible with MF-JSON¶

In [10]:
mf_json = traj.to_mf_json(temporal_columns=["preasure", "wind", "class"])
mf_json
Out[10]:
{'type': 'FeatureCollection',
 'features': [{'type': 'Feature',
   'properties': {'traj_id': 3},
   'temporalGeometry': {'type': 'MovingPoint',
    'coordinates': [(147.7, 15.6),
     (146.5, 16.1),
     (145.2, 16.8),
     (144.0, 17.5),
     (143.4, 18.0),
     (142.8, 18.5),
     (142.4, 18.9),
     (142.1, 19.7),
     (141.9, 20.5),
     (141.4, 21.1),
     (141.0, 21.5),
     (140.8, 21.7),
     (140.5, 21.9),
     (140.6, 22.1),
     (140.7, 22.1),
     (141.0, 22.0),
     (141.1, 22.0),
     (141.3, 22.0),
     (141.4, 22.2),
     (141.4, 22.3),
     (141.3, 22.7),
     (141.0, 22.9),
     (140.7, 22.8),
     (140.5, 22.7),
     (140.3, 23.1),
     (139.8, 23.6),
     (139.2, 24.0),
     (138.5, 24.2),
     (138.0, 24.7),
     (137.7, 25.0),
     (137.1, 25.9),
     (136.4, 26.8),
     (135.2, 27.4),
     (134.7, 27.6),
     (134.3, 28.1),
     (133.5, 28.3),
     (133.4, 28.4),
     (133.2, 28.8),
     (133.1, 29.1),
     (133.1, 29.6),
     (133.0, 30.1),
     (132.9, 30.6),
     (132.7, 30.8),
     (132.6, 31.7),
     (132.6, 32.2),
     (132.4, 32.7),
     (132.3, 33.4),
     (132.3, 33.6),
     (132.6, 34.3),
     (133.0, 35.3),
     (133.1, 36.2),
     (133.5, 38.0),
     (134.3, 39.9),
     (135.6, 40.9),
     (138.0, 43.0),
     (138.5, 43.7),
     (140.1, 44.4),
     (140.5, 44.7),
     (141.0, 45.1)],
    'datetimes': ['2019-08-05 00:00:00',
     '2019-08-05 06:00:00',
     '2019-08-05 12:00:00',
     '2019-08-05 18:00:00',
     '2019-08-06 00:00:00',
     '2019-08-06 06:00:00',
     '2019-08-06 12:00:00',
     '2019-08-06 18:00:00',
     '2019-08-07 00:00:00',
     '2019-08-07 06:00:00',
     '2019-08-07 12:00:00',
     '2019-08-07 18:00:00',
     '2019-08-08 00:00:00',
     '2019-08-08 06:00:00',
     '2019-08-08 12:00:00',
     '2019-08-08 18:00:00',
     '2019-08-09 00:00:00',
     '2019-08-09 06:00:00',
     '2019-08-09 12:00:00',
     '2019-08-09 18:00:00',
     '2019-08-10 00:00:00',
     '2019-08-10 06:00:00',
     '2019-08-10 12:00:00',
     '2019-08-10 18:00:00',
     '2019-08-11 00:00:00',
     '2019-08-11 06:00:00',
     '2019-08-11 12:00:00',
     '2019-08-11 18:00:00',
     '2019-08-12 00:00:00',
     '2019-08-12 06:00:00',
     '2019-08-12 12:00:00',
     '2019-08-12 18:00:00',
     '2019-08-13 00:00:00',
     '2019-08-13 06:00:00',
     '2019-08-13 12:00:00',
     '2019-08-13 18:00:00',
     '2019-08-13 21:00:00',
     '2019-08-14 00:00:00',
     '2019-08-14 03:00:00',
     '2019-08-14 06:00:00',
     '2019-08-14 09:00:00',
     '2019-08-14 12:00:00',
     '2019-08-14 15:00:00',
     '2019-08-14 18:00:00',
     '2019-08-14 21:00:00',
     '2019-08-15 00:00:00',
     '2019-08-15 02:00:00',
     '2019-08-15 03:00:00',
     '2019-08-15 06:00:00',
     '2019-08-15 09:00:00',
     '2019-08-15 12:00:00',
     '2019-08-15 18:00:00',
     '2019-08-16 00:00:00',
     '2019-08-16 06:00:00',
     '2019-08-16 12:00:00',
     '2019-08-16 18:00:00',
     '2019-08-17 00:00:00',
     '2019-08-17 06:00:00',
     '2019-08-17 12:00:00']},
   'temporalProperties': [{'datetimes': ['2019-08-05 00:00:00',
      '2019-08-05 06:00:00',
      '2019-08-05 12:00:00',
      '2019-08-05 18:00:00',
      '2019-08-06 00:00:00',
      '2019-08-06 06:00:00',
      '2019-08-06 12:00:00',
      '2019-08-06 18:00:00',
      '2019-08-07 00:00:00',
      '2019-08-07 06:00:00',
      '2019-08-07 12:00:00',
      '2019-08-07 18:00:00',
      '2019-08-08 00:00:00',
      '2019-08-08 06:00:00',
      '2019-08-08 12:00:00',
      '2019-08-08 18:00:00',
      '2019-08-09 00:00:00',
      '2019-08-09 06:00:00',
      '2019-08-09 12:00:00',
      '2019-08-09 18:00:00',
      '2019-08-10 00:00:00',
      '2019-08-10 06:00:00',
      '2019-08-10 12:00:00',
      '2019-08-10 18:00:00',
      '2019-08-11 00:00:00',
      '2019-08-11 06:00:00',
      '2019-08-11 12:00:00',
      '2019-08-11 18:00:00',
      '2019-08-12 00:00:00',
      '2019-08-12 06:00:00',
      '2019-08-12 12:00:00',
      '2019-08-12 18:00:00',
      '2019-08-13 00:00:00',
      '2019-08-13 06:00:00',
      '2019-08-13 12:00:00',
      '2019-08-13 18:00:00',
      '2019-08-13 21:00:00',
      '2019-08-14 00:00:00',
      '2019-08-14 03:00:00',
      '2019-08-14 06:00:00',
      '2019-08-14 09:00:00',
      '2019-08-14 12:00:00',
      '2019-08-14 15:00:00',
      '2019-08-14 18:00:00',
      '2019-08-14 21:00:00',
      '2019-08-15 00:00:00',
      '2019-08-15 02:00:00',
      '2019-08-15 03:00:00',
      '2019-08-15 06:00:00',
      '2019-08-15 09:00:00',
      '2019-08-15 12:00:00',
      '2019-08-15 18:00:00',
      '2019-08-16 00:00:00',
      '2019-08-16 06:00:00',
      '2019-08-16 12:00:00',
      '2019-08-16 18:00:00',
      '2019-08-17 00:00:00',
      '2019-08-17 06:00:00',
      '2019-08-17 12:00:00'],
     'preasure': {'values': [1004.0,
       1000.0,
       1000.0,
       1000.0,
       1000.0,
       998.0,
       996.0,
       992.0,
       990.0,
       990.0,
       980.0,
       975.0,
       970.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       970.0,
       970.0,
       970.0,
       970.0,
       970.0,
       970.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       965.0,
       970.0,
       975.0,
       975.0,
       975.0,
       975.0,
       978.0,
       978.0,
       980.0,
       980.0,
       980.0,
       980.0,
       984.0,
       986.0,
       990.0,
       994.0,
       994.0]},
     'wind': {'values': [0.0,
       0.0,
       0.0,
       0.0,
       0.0,
       35.0,
       40.0,
       45.0,
       50.0,
       50.0,
       60.0,
       65.0,
       70.0,
       75.0,
       75.0,
       75.0,
       75.0,
       75.0,
       75.0,
       70.0,
       70.0,
       65.0,
       65.0,
       65.0,
       65.0,
       65.0,
       60.0,
       60.0,
       60.0,
       50.0,
       50.0,
       50.0,
       50.0,
       50.0,
       50.0,
       55.0,
       55.0,
       55.0,
       55.0,
       55.0,
       55.0,
       55.0,
       55.0,
       55.0,
       55.0,
       50.0,
       50.0,
       50.0,
       45.0,
       45.0,
       45.0,
       45.0,
       45.0,
       45.0,
       0.0,
       0.0,
       0.0,
       0.0,
       0.0]},
     'class': {'values': [2.0,
       2.0,
       2.0,
       2.0,
       2.0,
       3.0,
       3.0,
       3.0,
       4.0,
       4.0,
       4.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       5.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       4.0,
       3.0,
       3.0,
       3.0,
       3.0,
       3.0,
       3.0,
       6.0,
       6.0,
       6.0,
       6.0,
       6.0]}}]}]}

Save MF-JSON dict to file¶

In [11]:
with open("../data/mf1.json", "w") as json_file:
    json.dump(mf_json, json_file, indent=4)

Read MF-JSON file¶

In [12]:
traj = (
    mpd.read_mf_json("../data/mf1.json", traj_id_property="traj_id").trajectories[0].df
)
traj.head()
Out[12]:
preasure wind class geometry traj_id
t
2019-08-05 00:00:00 1004.0 0.0 2.0 POINT (147.7 15.6) 3
2019-08-05 06:00:00 1000.0 0.0 2.0 POINT (146.5 16.1) 3
2019-08-05 12:00:00 1000.0 0.0 2.0 POINT (145.2 16.8) 3
2019-08-05 18:00:00 1000.0 0.0 2.0 POINT (144 17.5) 3
2019-08-06 00:00:00 1000.0 0.0 2.0 POINT (143.4 18) 3
In [13]:
traj.explore()
Out[13]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Convert GeoDataFrame to a dict compatible with MF-JSON¶

In [14]:
df = read_file("../data/geolife_small.csv")
gdf = GeoDataFrame(df, geometry=gpd.points_from_xy(df["X"], df["Y"]))
gdf.head()
Out[14]:
X Y fid id sequence trajectory_id tracker t geometry
0 116.391305 39.898573 1 1 1 1 19 2008-12-11 04:42:14+00 POINT (116.3913 39.89857)
1 116.391317 39.898617 2 2 2 1 19 2008-12-11 04:42:16+00 POINT (116.39132 39.89862)
2 116.390928 39.898613 3 3 3 1 19 2008-12-11 04:43:26+00 POINT (116.39093 39.89861)
3 116.390833 39.898635 4 4 4 1 19 2008-12-11 04:43:32+00 POINT (116.39083 39.89864)
4 116.38941 39.898723 5 5 5 1 19 2008-12-11 04:43:47+00 POINT (116.38941 39.89872)
In [15]:
mf_json = gdf_to_mf_json(gdf, traj_id_column="trajectory_id", datetime_column="t")

Save MF-JSON dict to file¶

In [16]:
with open("../data/mf-geolife_small.json", "w") as json_file:
    json.dump(mf_json, json_file, indent=4)

Read JSON file to dict¶

In [17]:
with open("../data/mf-geolife_small.json", "r") as file:
    data = json.load(file)
In [18]:
s = list(data.items())[:2]
s = str(s)
s[:1000]
Out[18]:
"[('type', 'FeatureCollection'), ('features', [{'type': 'Feature', 'properties': {'trajectory_id': '1', 'X': '116.391305', 'Y': '39.898573', 'fid': '1', 'id': '1', 'sequence': '1', 'tracker': '19'}, 'temporalGeometry': {'type': 'MovingPoint', 'coordinates': [[116.391305, 39.898573], [116.391317, 39.898617], [116.390928, 39.898613], [116.390833, 39.898635], [116.38941, 39.898723], [116.390522, 39.897928], [116.390608, 39.897837], [116.390708, 39.8979], [116.390725, 39.89801], [116.390747, 39.898107], [116.390805, 39.898187], [116.39086, 39.898287], [116.390913, 39.898398], [116.390827, 39.898373], [116.390695, 39.898377], [116.390598, 39.898382], [116.390505, 39.898408], [116.39041, 39.898417], [116.390303, 39.898428], [116.390193, 39.898437], [116.390127, 39.898375], [116.39003, 39.89834], [116.389932, 39.898292], [116.389837, 39.898238], [116.389738, 39.898182], [116.389652, 39.898123], [116.389572, 39.898078], [116.389427, 39.898013], [116.389335, 39.89797], [116.389237, 39.897933], ["

Read MF-JSON from a dict¶

In [19]:
tc = read_mf_dict(data, traj_id_property="trajectory_id")
tc
Out[19]:
TrajectoryCollection with 5 trajectories

Read MF-JSON file¶

In [20]:
tc = mpd.read_mf_json("../data/mf-geolife_small.json", traj_id_property="trajectory_id")
tc
Out[20]:
TrajectoryCollection with 5 trajectories
In [21]:
tc.explore(column="trajectory_id", cmap="viridis", style_kwds={"weight": 4})
Out[21]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [ ]: