mapillary / mapillary-python-sdk

A Python 3 library built on the Mapillary API v4 to facilitate retrieving and working with Mapillary data.
MIT License
37 stars 14 forks source link

Mapillary API v4 map features #137

Closed sandhawalia closed 2 years ago

sandhawalia commented 2 years ago

Hey folks,

Thanks for this python SDK. Amazing work here. We are trying to fetch some map features from the API v4 and ran into some issues. Here is a script to reproduce the error we got and a sample geoJSON to fetch features with. The two errors were

  1. Due to concatenating a list and None returned by pipeline_component (In try / catch)
  2. value key missing when a Polygon with empty properties is returned by the API endpoint.

Sample script

import json
import tqdm
import geojson
import argparse
from pathlib import Path
from shapely.geometry import shape

from mapillary.interface import (
    set_access_token,
    traffic_signs_in_bbox,
    map_feature_points_in_bbox,
    save_locally,
)

map_feature_whitelist = [
    "construction--flat--driveway",
    "marking--discrete--stop-line",
    "marking--discrete--give-way-single",
    "marking--discrete--give-way-row",
]
traffic_sign_whitelist = [
    "regulatory--yield--g1",
    "warning--yield-ahead--g1",
    "warning--yield-ahead--g3",
    "information--bus-stop--g1",
    "information--bus-stop--g2",
    "warning--traffic-signals--g1",
    "regulatory--priority-road--g1",
    "regulatory--end-of-priority-road--g1",
    "regulatory--end-of-priority-road--g2",
    "regulatory--give-way-to-oncoming-traffic--g1",
    "regulatory--give-way-to-oncoming-traffic--g2",
    "regulatory--yield-or-stop-for-pedestrians--g1",
    "complementary--priority-route-at-intersection--g2",
    "complementary--priority-route-at-intersection--g3",
    "complementary--priority-route-at-intersection--g4",
    "complementary--priority-route-at-intersection--g5",
    "complementary--priority-route-at-intersection--g6",
]

def get_mapillary_features(access_token, src_json, destination_json):

    with src_json.open() as pfile:
        map_polygons = geojson.load(pfile)

    features = map_polygons["features"]

    set_access_token(access_token)
    print("Set up Access to Mapillary")
    print("Fetching data from Mapillary")

    pbar = tqdm.tqdm(features)

    features_of_interest = []

    for feature in pbar:

        try:

            box = shape(feature["geometry"]).bounds

            map_features = map_feature_points_in_bbox(
                bbox={
                    'west': box[0],
                    'south': box[1],
                    'east': box[2],
                    'north': box[3]
                    },
                filter_values=map_feature_whitelist
            )
            map_signage = traffic_signs_in_bbox(
                bbox={
                    'west': box[0],
                    'south': box[1],
                    'east': box[2],
                    'north': box[3]
                    },
                filter_values=traffic_sign_whitelist
            )

            map_features = json.loads(map_features)
            map_signage = json.loads(map_signage)

            features_of_interest.extend(map_features["features"] + map_signage["features"])

            pbar.set_description(f"{box}, {len(features_of_interest)}")

        except Exception as err:
            print(f"Error processing {box}, {err}")

    merged = {
        "type": "FeatureCollection",
        "features": features_of_interest,
    }
    merged = json.dumps(merged)
    print("Got data from Mapillary")
    save_locally(
        geojson_data=merged,
        file_path=destination_json.parent,
        file_name=destination_json.stem,
        extension="geojson",
    )
    print(f"Saved at {destination_json}")

if __name__ == "__main__":

    parser = argparse.ArgumentParser("Fetch Mapillary features")
    parser.add_argument("-a", "--access-token", help="Access token", type=str)
    parser.add_argument(
        "-s",
        "--src-json",
        help="geoJSON with map regions",
        type=Path,
    )
    parser.add_argument(
        "-d",
        "--dst-json",
        help="Destination geoJSON",
        type=Path,
    )

    args = parser.parse_args()

    get_mapillary_features(args.access_token, args.src_json, args.dst_json)

Sample --src-json

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              12.98012,
              52.509091
            ],
            [
              13.152815,
              52.509091
            ],
            [
              13.152815,
              52.601349
            ],
            [
              12.98012,
              52.601349
            ],
            [
              12.98012,
              52.509091
            ]
          ]
        ]
      },
      "properties": {}
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              13.073284,
              52.41406
            ],
            [
              13.160322,
              52.41406
            ],
            [
              13.160322,
              52.51878
            ],
            [
              13.073284,
              52.51878
            ],
            [
              13.073284,
              52.41406
            ]
          ]
        ]
      },
      "properties": {}
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              13.069598,
              52.370633
            ],
            [
              13.156459,
              52.370633
            ],
            [
              13.156459,
              52.432365
            ],
            [
              13.069598,
              52.432365
            ],
            [
              13.069598,
              52.370633
            ]
          ]
        ]
      },
      "properties": {}
    }
  ]
}

Sample Polygon

Returned by the API.

{
  "type": "Feature",
  "geometry": {
    "type": "Polygon",
    "coordinates": [
      [
        [
          13.689651489257812,
          52.40283780245278
        ],
        [
          13.689651489257812,
          52.38859200247748
        ],
        [
          13.666305541992188,
          52.38859200247748
        ],
        [
          13.666305541992188,
          52.40283780245278
        ],
        [
          13.689651489257812,
          52.40283780245278
        ]
      ]
    ]
  },
  "properties": {}
}
facebook-github-bot commented 2 years ago

Hi @sandhawalia!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks!

Rubix982 commented 2 years ago

Hi, @sandhawalia! Thanks for the PR and the kind words!

The changes seem alright to me, but I'm looking forward to adding more tests to this because I realized our code coverage is low. This addition seems to me to introduce no breaking changes, and so can be merged.

Thank you for your contribution!

Please tag me here once you've signed the CLA for Meta as mentioned by @facebook-github-bot.

sandhawalia commented 2 years ago

Hi @Rubix982 signed the CLA. Lemme know if you need help with tests.

Rubix982 commented 2 years ago

Alright, thank you!

Rubix982 commented 2 years ago

I feel the failed documentation build is due to no actual changes in the documentation from the python docstrings, so it's all good. The documentation is still up on https://mapillary.github.io/mapillary-python-sdk.